mlabonne commited on
Commit
ba6030d
·
verified ·
1 Parent(s): c840043

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +348 -0
app.py ADDED
@@ -0,0 +1,348 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import time
3
+ import random
4
+ import yaml
5
+ import subprocess
6
+
7
+ import runpod
8
+ # import gradio as gr
9
+ import pandas as pd
10
+ from jinja2 import Template
11
+ from huggingface_hub import ModelCard, ModelCardData, HfApi, repo_info
12
+ from huggingface_hub.utils import RepositoryNotFoundError
13
+
14
+ # Set environment variables
15
+ HF_TOKEN = os.environ.get("HF_TOKEN")
16
+ runpod.api_key = os.environ.get("RUNPOD_TOKEN")
17
+
18
+ # Parameters
19
+ USERNAME = 'automerger'
20
+ N_ROWS = 20
21
+ WAIT_TIME = 3600
22
+
23
+
24
+ def create_dataset() -> bool:
25
+ """
26
+ Use Scrape Open LLM Leaderboard to create a CSV dataset.
27
+ """
28
+ command = ["python3", "scrape-open-llm-leaderboard/main.py", "-csv"]
29
+
30
+ try:
31
+ result = subprocess.run(command, check=True, stdout=subprocess.PIPE,
32
+ stderr=subprocess.PIPE, text=True)
33
+ print(f"scrape-open-llm-leaderboard: {result.stdout}")
34
+ return True
35
+ except subprocess.CalledProcessError as e:
36
+ print(f"scrape-open-llm-leaderboard: {e.stderr}")
37
+ return False
38
+
39
+
40
+ def merge_models() -> None:
41
+ """
42
+ Use mergekit to create a merge.
43
+ """
44
+ command = ["mergekit-yaml", "config.yaml", "merge", "--copy-tokenizer"]
45
+
46
+ try:
47
+ result = subprocess.run(command, check=True, stdout=subprocess.PIPE,
48
+ stderr=subprocess.PIPE, text=True)
49
+ print(f"mergekit: {result.stdout}")
50
+ except subprocess.CalledProcessError as e:
51
+ print(f"mergekit: {e.stderr}")
52
+
53
+
54
+ def make_df(file_path: str, n_rows: int) -> pd.DataFrame:
55
+ """
56
+ Create a filtered dataset from the Open LLM Leaderboard.
57
+ """
58
+ columns = ["Available on the hub", "Model sha", "T", "Type", "Precision",
59
+ "Architecture", "Weight type", "Hub ❤️", "Flagged", "MoE"]
60
+ ds = pd.read_csv(file_path)
61
+ df = (
62
+ ds[
63
+ (ds["#Params (B)"] == 7.24) &
64
+ (ds["Available on the hub"] == True) &
65
+ (ds["Flagged"] == False) &
66
+ (ds["MoE"] == False) &
67
+ (ds["Weight type"] == "Original")
68
+ ]
69
+ .drop(columns=columns)
70
+ .drop_duplicates(subset=["Model"])
71
+ .iloc[:n_rows]
72
+ )
73
+ return df
74
+
75
+
76
+ def repo_exists(repo_id: str) -> bool:
77
+ try:
78
+ repo_info(repo_id)
79
+ return True
80
+ except RepositoryNotFoundError:
81
+ return False
82
+
83
+
84
+ def get_name(models: list[pd.Series], username: str, version=0) -> str:
85
+ model_name = models[0]["Model"].split("/")[-1].split("-")[0].capitalize() \
86
+ + models[1]["Model"].split("/")[-1].split("-")[0].capitalize() \
87
+ + "-7B"
88
+ if version > 0:
89
+ model_name = model_name.split("-")[0] + f"-v{version}-7B"
90
+
91
+ if repo_exists(f"{username}/{model_name}"):
92
+ get_name(models, username, version+1)
93
+
94
+ return model_name
95
+
96
+
97
+ def get_license(models: list[pd.Series]) -> str:
98
+ license1 = models[0]["Hub License"]
99
+ license2 = models[1]["Hub License"]
100
+ license = "cc-by-nc-4.0"
101
+
102
+ if license1 == "cc-by-nc-4.0" or license2 == "cc-by-nc-4.0":
103
+ license = "cc-by-nc-4.0"
104
+ elif license1 == "apache-2.0" or license2 == "apache-2.0":
105
+ license = "apache-2.0"
106
+ elif license1 == "MIT" and license2 == "MIT":
107
+ license = "MIT"
108
+ return license
109
+
110
+
111
+ def create_config(models: list[pd.Series]) -> str:
112
+ slerp_config = f"""
113
+ slices:
114
+ - sources:
115
+ - model: {models[0]["Model"]}
116
+ layer_range: [0, 32]
117
+ - model: {models[1]["Model"]}
118
+ layer_range: [0, 32]
119
+ merge_method: slerp
120
+ base_model: {models[0]["Model"]}
121
+ parameters:
122
+ t:
123
+ - filter: self_attn
124
+ value: [0, 0.5, 0.3, 0.7, 1]
125
+ - filter: mlp
126
+ value: [1, 0.5, 0.7, 0.3, 0]
127
+ - value: 0.5
128
+ dtype: bfloat16
129
+ random_seed: 0
130
+ """
131
+ dare_config = f"""
132
+ models:
133
+ - model: {models[0]["Model"]}
134
+ # No parameters necessary for base model
135
+ - model: {models[1]["Model"]}
136
+ parameters:
137
+ density: 0.53
138
+ weight: 0.6
139
+ merge_method: dare_ties
140
+ base_model: {models[0]["Model"]}
141
+ parameters:
142
+ int8_mask: true
143
+ dtype: bfloat16
144
+ random_seed: 0
145
+ """
146
+ yaml_config = random.choices([slerp_config, dare_config], weights=[0.4, 0.6], k=1)[0]
147
+
148
+ with open('config.yaml', 'w', encoding="utf-8") as f:
149
+ f.write(yaml_config)
150
+
151
+ return yaml_config
152
+
153
+
154
+ def create_model_card(yaml_config: str, model_name: str, username: str, license: str) -> None:
155
+ template_text = """
156
+ ---
157
+ license: {{ license }}
158
+ base_model:
159
+ {%- for model in models %}
160
+ - {{ model }}
161
+ {%- endfor %}
162
+ tags:
163
+ - merge
164
+ - mergekit
165
+ - lazymergekit
166
+ ---
167
+
168
+ # {{ model_name }}
169
+
170
+ {{ model_name }} is an automated merge created by [Maxime Labonne](https://huggingface.co/mlabonne) using the following configuration.
171
+
172
+ {%- for model in models %}
173
+ * [{{ model }}](https://huggingface.co/{{ model }})
174
+ {%- endfor %}
175
+
176
+ ## 🧩 Configuration
177
+
178
+ ```yaml
179
+ {{- yaml_config -}}
180
+ ```
181
+
182
+ ## 💻 Usage
183
+
184
+ ```python
185
+ !pip install -qU transformers accelerate
186
+
187
+ from transformers import AutoTokenizer
188
+ import transformers
189
+ import torch
190
+
191
+ model = "{{ username }}/{{ model_name }}"
192
+ messages = [{"role": "user", "content": "What is a large language model?"}]
193
+
194
+ tokenizer = AutoTokenizer.from_pretrained(model)
195
+ prompt = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
196
+ pipeline = transformers.pipeline(
197
+ "text-generation",
198
+ model=model,
199
+ torch_dtype=torch.float16,
200
+ device_map="auto",
201
+ )
202
+
203
+ outputs = pipeline(prompt, max_new_tokens=256, do_sample=True, temperature=0.7, top_k=50, top_p=0.95)
204
+ print(outputs[0]["generated_text"])
205
+ ```
206
+ """
207
+
208
+ # Create a Jinja template object
209
+ jinja_template = Template(template_text.strip())
210
+
211
+ # Get list of models from config
212
+ data = yaml.safe_load(yaml_config)
213
+ if "models" in data:
214
+ models = [data["models"][i]["model"] for i in range(len(data["models"])) if "parameters" in data["models"][i]]
215
+ elif "parameters" in data:
216
+ models = [data["slices"][0]["sources"][i]["model"] for i in range(len(data["slices"][0]["sources"]))]
217
+ elif "slices" in data:
218
+ models = [data["slices"][i]["sources"][0]["model"] for i in range(len(data["slices"]))]
219
+ else:
220
+ raise Exception("No models or slices found in yaml config")
221
+
222
+ # Fill the template
223
+ content = jinja_template.render(
224
+ model_name=model_name,
225
+ models=models,
226
+ yaml_config=yaml_config,
227
+ username=username,
228
+ license=license
229
+ )
230
+
231
+ # Save the model card
232
+ card = ModelCard(content)
233
+ card.save('merge/README.md')
234
+
235
+
236
+ def upload_model(api: HfApi, username: str, model_name: str) -> None:
237
+ api.create_repo(
238
+ repo_id=f"{username}/{model_name}",
239
+ repo_type="model",
240
+ exist_ok=True,
241
+ )
242
+ api.upload_folder(
243
+ repo_id=f"{username}/{model_name}",
244
+ folder_path="merge",
245
+ )
246
+
247
+
248
+ def create_pod(model_name: str, username: str, n=10, wait_seconds=10):
249
+ for attempt in range(n):
250
+ try:
251
+ pod = runpod.create_pod(
252
+ name=f"Automerge {model_name} on Nous",
253
+ image_name="runpod/pytorch:2.0.1-py3.10-cuda11.8.0-devel-ubuntu22.04",
254
+ gpu_type_id="NVIDIA GeForce RTX 3090",
255
+ cloud_type="COMMUNITY",
256
+ gpu_count=1,
257
+ volume_in_gb=0,
258
+ container_disk_in_gb=50,
259
+ template_id="au6nz6emhk",
260
+ env={
261
+ "BENCHMARK": "nous",
262
+ "MODEL_ID": f"{username}/{model_name}",
263
+ "REPO": "https://github.com/mlabonne/llm-autoeval.git",
264
+ "TRUST_REMOTE_CODE": False,
265
+ "DEBUG": False,
266
+ "GITHUB_API_TOKEN": os.environ["GITHUB_TOKEN"],
267
+ }
268
+ )
269
+ print("Pod creation succeeded.")
270
+ return pod
271
+ except Exception as e:
272
+ print(f"Attempt {attempt + 1} failed with error: {e}")
273
+ if attempt < n - 1:
274
+ print(f"Waiting {wait_seconds} seconds before retrying...")
275
+ time.sleep(wait_seconds)
276
+ else:
277
+ print("All attempts failed. Giving up.")
278
+ raise
279
+
280
+ def merge_loop():
281
+ # Start HF API
282
+ api = HfApi(token=HF_TOKEN)
283
+
284
+ # Create dataset (proceed only if successful)
285
+ if not create_dataset():
286
+ print("Failed to create dataset. Skipping merge loop.")
287
+ return
288
+
289
+ df = make_df("open-llm-leaderboard.csv", N_ROWS)
290
+
291
+ # Sample two models
292
+ sample = df.sample(n=2)
293
+ models = [sample.iloc[i] for i in range(2)]
294
+
295
+ # Get model name
296
+ model_name = get_name(models, USERNAME, version=0)
297
+ print(model_name)
298
+
299
+ # Get model license
300
+ license = get_license(models)
301
+ print(license)
302
+
303
+ # Merge configs
304
+ yaml_config = create_config(models)
305
+ print(yaml_config)
306
+
307
+ # Merge models
308
+ merge_models()
309
+
310
+ # Create model card
311
+ create_model_card(yaml_config, model_name, USERNAME, license)
312
+
313
+ # Upload model
314
+ upload_model(api, USERNAME, model_name)
315
+
316
+ # Evaluate model on Runpod
317
+ create_pod(model_name, USERNAME)
318
+
319
+ command = ["git", "clone", "-q", "https://github.com/Weyaxi/scrape-open-llm-leaderboard"]
320
+ subprocess.run(command, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
321
+
322
+ command = ["pip", "install", "-r", "scrape-open-llm-leaderboard/requirements.txt"]
323
+ subprocess.run(command, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
324
+
325
+ command = ["git", "clone", "https://github.com/arcee-ai/mergekit.git"]
326
+ subprocess.run(command, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
327
+
328
+ command = ["pip", "install", "-e", "mergekit"]
329
+ subprocess.run(command, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
330
+
331
+ # Gradio interface
332
+ title = """
333
+ <div align="center">
334
+ <p style="font-size: 36px;">♾️ AutoMerger</p>
335
+ <p style="font-size: 20px;">📝 <a href="https://medium.com/towards-data-science/merge-large-language-models-with-mergekit-2118fb392b54">Model merging</a> • 💻 <a href="https://github.com/arcee-ai/mergekit">Mergekit</a> • 🐦 <a href="https://twitter.com/maximelabonne">Follow me on X</a></p>
336
+ <p><em>AutoMerger selects two 7B models on top of the Open LLM Leaderboard, combine them with a merge technique, and evaluate the resulting model.</em></p>
337
+ </div>
338
+ """
339
+ # with gr.Blocks() as demo:
340
+ # gr.Markdown(title)
341
+ # demo.launch().launch(server_name="0.0.0.0")
342
+
343
+ print("Start AutoMerger...")
344
+
345
+ # Main loop
346
+ while True:
347
+ merge_loop()
348
+ time.sleep(WAIT_TIME)