broadfield-dev commited on
Commit
6a6f272
Β·
verified Β·
1 Parent(s): 66d5a98

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +116 -191
app.py CHANGED
@@ -7,13 +7,14 @@ import tempfile
7
  import shutil
8
  import subprocess
9
  from datetime import datetime
 
10
  from huggingface_hub import HfApi
11
  from transformers import AutoConfig, AutoModel, AutoTokenizer
12
  from optimum.onnxruntime import ORTQuantizer
13
  from optimum.onnxruntime.configuration import AutoQuantizationConfig
14
- from optimum.exporters.gguf import main_export as gguf_export
15
  import torch.nn.utils.prune as prune
16
 
 
17
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
18
 
19
  HF_TOKEN = os.getenv("HF_TOKEN")
@@ -24,30 +25,55 @@ api = HfApi()
24
  OUTPUT_DIR = "optimized_models"
25
  os.makedirs(OUTPUT_DIR, exist_ok=True)
26
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  def stage_1_analyze_model(model_id: str):
28
  log_stream = "[STAGE 1] Analyzing model...\n"
29
  try:
30
  config = AutoConfig.from_pretrained(model_id, trust_remote_code=True, token=HF_TOKEN)
31
  model_type = config.model_type
32
-
33
- analysis_report = f"""
34
- ### Model Analysis Report
35
- - **Model ID:** `{model_id}`
36
- - **Architecture:** `{model_type}`
37
- """
38
-
39
  recommendation = ""
40
  if 'llama' in model_type or 'gpt' in model_type or 'mistral' in model_type or 'gemma' in model_type:
41
- recommendation = "**Recommendation:** This is a Large Language Model (LLM). For the best CPU performance and community support, the **GGUF Pipeline** is highly recommended. The ONNX pipeline is a viable alternative."
42
  else:
43
- recommendation = "**Recommendation:** This is likely an encoder model. The **ONNX Pipeline** is recommended. Pruning may offer size reduction, but its impact on performance can vary."
44
-
45
  log_stream += f"Analysis complete. Architecture: {model_type}.\n"
46
  return log_stream, analysis_report + "\n" + recommendation, gr.Accordion(open=True)
47
  except Exception as e:
48
  error_msg = f"Failed to analyze model '{model_id}'. Error: {e}"
49
  logging.error(error_msg)
50
- return log_stream + error_msg, "Could not analyze model. Please check the model ID and try again.", gr.Accordion(open=False)
51
 
52
  def stage_2_prune_model(model, prune_percentage: float):
53
  if prune_percentage == 0:
@@ -67,115 +93,88 @@ def stage_3_4_onnx_quantize(model_path: str, calibration_data_path: str):
67
  onnx_path = os.path.join(OUTPUT_DIR, f"{model_name}-{run_id}-onnx")
68
 
69
  try:
70
- log_stream += "Executing `optimum-cli export onnx` via subprocess...\n"
71
- export_command = [
72
- "optimum-cli", "export", "onnx",
73
- "--model", model_path,
74
- "--trust-remote-code",
75
- onnx_path
76
- ]
77
  process = subprocess.run(export_command, check=True, capture_output=True, text=True)
78
  log_stream += process.stdout
79
  if process.stderr: log_stream += f"[STDERR]\n{process.stderr}\n"
80
- log_stream += f"Successfully exported base model to ONNX at: {onnx_path}\n"
81
  except subprocess.CalledProcessError as e:
82
- error_msg = f"Failed during `optimum-cli export onnx`. Error:\n{e.stderr}"
83
- logging.error(error_msg)
84
- raise RuntimeError(error_msg)
85
 
86
  try:
87
  quantizer = ORTQuantizer.from_pretrained(onnx_path)
88
-
89
  if calibration_data_path:
90
- log_stream += "Performing STATIC quantization with user-provided calibration data.\n"
91
  dqconfig = AutoQuantizationConfig.avx512_vnni(is_static=True, per_channel=False)
92
- from datasets import load_dataset
93
- calibration_dataset = quantizer.get_calibration_dataset(
94
- "text",
95
- dataset_args={"path": calibration_data_path, "split": "train"},
96
- num_samples=100,
97
- dataset_num_proc=1,
98
- )
99
  quantized_path = os.path.join(onnx_path, "quantized-static")
100
- quantizer.quantize(save_dir=quantized_path, quantization_config=dqconfig, calibration_dataset=calibration_dataset)
101
  else:
102
- log_stream += "Performing DYNAMIC quantization.\n"
103
  dqconfig = AutoQuantizationConfig.avx512_vnni(is_static=False, per_channel=False)
104
  quantized_path = os.path.join(onnx_path, "quantized-dynamic")
105
  quantizer.quantize(save_dir=quantized_path, quantization_config=dqconfig)
106
-
107
  log_stream += f"Successfully quantized model to: {quantized_path}\n"
108
  return quantized_path, log_stream
109
  except Exception as e:
110
- error_msg = f"Failed during ONNX quantization step. Error: {e}"
111
- logging.error(error_msg, exc_info=True)
112
- raise RuntimeError(error_msg)
113
 
114
  def stage_3_4_gguf_quantize(model_path: str, model_id: str, quantization_strategy: str):
115
- log_stream = f"[STAGE 3 & 4] Converting to GGUF with '{quantization_strategy}' quantization...\n"
116
  run_id = datetime.now().strftime("%Y%m%d-%H%M%S")
117
  model_name = model_id.replace('/', '_')
118
  gguf_path = os.path.join(OUTPUT_DIR, f"{model_name}-{run_id}-gguf")
119
  os.makedirs(gguf_path, exist_ok=True)
120
- output_file = os.path.join(gguf_path, "model.gguf")
 
 
121
 
122
  try:
123
- log_stream += "Calling `optimum.exporters.gguf.main_export` programmatically...\n"
124
- gguf_export(
125
- model_id_or_path=model_path,
126
- output=output_file,
127
- quantization_strategy=quantization_strategy,
128
- trust_remote_code=True
129
- )
130
- log_stream += f"Successfully exported and quantized model to GGUF at: {gguf_path}\n"
 
 
 
 
 
 
 
 
 
 
 
 
131
  return gguf_path, log_stream
 
 
132
  except Exception as e:
133
- error_msg = f"Failed during GGUF conversion. Error: {e}"
134
- logging.error(error_msg, exc_info=True)
135
- raise RuntimeError(error_msg)
136
-
137
 
138
  def stage_5_package_and_upload(model_id: str, optimized_model_path: str, pipeline_log: str, options: dict):
 
139
  log_stream = "[STAGE 5] Packaging and Uploading...\n"
140
  if not HF_TOKEN:
141
  return "Skipping upload: HF_TOKEN not found.", log_stream + "Skipping upload: HF_TOKEN not found."
142
-
143
  try:
144
  repo_name = f"{model_id.split('/')[-1]}-amop-cpu-{options['pipeline_type'].lower()}"
145
  repo_url = api.create_repo(repo_id=repo_name, exist_ok=True, token=HF_TOKEN)
146
-
147
- if options['pipeline_type'] == "GGUF":
148
- template_file = "model_card_template_gguf.md"
149
- else:
150
- template_file = "model_card_template.md"
151
-
152
- with open(template_file, "r", encoding="utf-8") as f:
153
- template_content = f.read()
154
-
155
- model_card_content = template_content.format(
156
- repo_name=repo_name, model_id=model_id, optimization_date=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
157
- pruning_status="Enabled" if options.get('prune', False) else "Disabled",
158
- pruning_percent=options.get('prune_percent', 0),
159
- quant_type=options.get('quant_type', 'N/A'),
160
- repo_id=repo_url.repo_id, pipeline_log=pipeline_log
161
- )
162
- readme_path = os.path.join(optimized_model_path, "README.md")
163
- with open(readme_path, "w", encoding="utf-8") as f:
164
- f.write(model_card_content)
165
-
166
  if options['pipeline_type'] == "ONNX":
167
- tokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)
168
- tokenizer.save_pretrained(optimized_model_path)
169
-
170
  api.upload_folder(folder_path=optimized_model_path, repo_id=repo_url.repo_id, repo_type="model", token=HF_TOKEN)
171
-
172
- final_message = f"Success! Your optimized model is available at: huggingface.co/{repo_url.repo_id}"
173
  log_stream += "Upload complete.\n"
174
- return final_message, log_stream
175
  except Exception as e:
176
- error_msg = f"Failed to upload to the Hub. Error: {e}"
177
- logging.error(error_msg, exc_info=True)
178
- return f"Error: {error_msg}", log_stream + error_msg
179
 
180
  def run_amop_pipeline(model_id: str, pipeline_type: str, do_prune: bool, prune_percent: float, onnx_quant_type: str, calibration_file, gguf_quant_type: str):
181
  if not model_id:
@@ -183,165 +182,91 @@ def run_amop_pipeline(model_id: str, pipeline_type: str, do_prune: bool, prune_p
183
  return
184
 
185
  initial_log = f"[START] AMOP {pipeline_type} Pipeline Initiated.\n"
186
- yield {
187
- run_button: gr.Button(interactive=False, value="πŸš€ Running..."),
188
- analyze_button: gr.Button(interactive=False),
189
- final_output: f"RUNNING ({pipeline_type})",
190
- log_output: initial_log
191
- }
192
-
193
  full_log = initial_log
194
  temp_model_dir = None
195
  try:
196
- repo_name_suffix = f"-amop-cpu-{pipeline_type.lower()}"
197
  whoami = api.whoami()
198
- if not whoami:
199
- raise RuntimeError("Could not authenticate with Hugging Face Hub. Check your HF_TOKEN.")
200
- repo_id_for_link = f"{whoami['name']}/{model_id.split('/')[-1]}{repo_name_suffix}"
201
 
202
- full_log += "Loading base model...\n"
203
- yield {final_output: "Loading model (1/5)", log_output: full_log}
204
  model = AutoModel.from_pretrained(model_id, trust_remote_code=True)
205
  tokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)
206
- full_log += f"Successfully loaded base model '{model_id}'.\n"
207
-
208
- yield {final_output: "Pruning model (2/5)", log_output: full_log}
209
- if do_prune:
210
- model, log = stage_2_prune_model(model, prune_percent)
211
- full_log += log
212
- else:
213
- full_log += "[STAGE 2] Pruning skipped by user.\n"
214
 
 
 
 
 
215
  temp_model_dir = tempfile.mkdtemp()
216
  model.save_pretrained(temp_model_dir)
217
  tokenizer.save_pretrained(temp_model_dir)
218
- full_log += f"Saved intermediate model to temporary directory: {temp_model_dir}\n"
219
 
220
  if pipeline_type == "ONNX":
221
- yield {final_output: "Converting to ONNX (3/5)", log_output: full_log}
222
- calib_path = calibration_file.name if onnx_quant_type == "Static" and calibration_file else None
223
- optimized_path, log = stage_3_4_onnx_quantize(temp_model_dir, calib_path)
224
- full_log += log
225
  options = {'pipeline_type': 'ONNX', 'prune': do_prune, 'prune_percent': prune_percent, 'quant_type': onnx_quant_type}
226
-
227
  elif pipeline_type == "GGUF":
228
- yield {final_output: "Converting to GGUF (3/5)", log_output: full_log}
229
  optimized_path, log = stage_3_4_gguf_quantize(temp_model_dir, model_id, gguf_quant_type)
230
- full_log += log
231
  options = {'pipeline_type': 'GGUF', 'prune': do_prune, 'prune_percent': prune_percent, 'quant_type': gguf_quant_type}
232
-
233
  else:
234
  raise ValueError("Invalid pipeline type selected.")
235
-
236
- yield {final_output: "Packaging & Uploading (4/5)", log_output: full_log}
 
237
  final_message, log = stage_5_package_and_upload(model_id, optimized_path, full_log, options)
238
  full_log += log
239
 
240
- yield {
241
- final_output: gr.update(value="SUCCESS", label="Status"),
242
- log_output: full_log,
243
- success_box: gr.Markdown(f"βœ… **Success!** Your optimized model is available here: [{repo_id_for_link}](https://huggingface.co/{repo_id_for_link})", visible=True),
244
- run_button: gr.Button(interactive=True, value="Run Optimization Pipeline", variant="primary"),
245
- analyze_button: gr.Button(interactive=True, value="Analyze Model")
246
- }
247
-
248
  except Exception as e:
249
  logging.error(f"AMOP Pipeline failed. Error: {e}", exc_info=True)
250
  full_log += f"\n[ERROR] Pipeline failed: {e}"
251
- yield {
252
- final_output: gr.update(value="ERROR", label="Status"),
253
- log_output: full_log,
254
- success_box: gr.Markdown(f"❌ **An error occurred.** Check the logs for details.", visible=True),
255
- run_button: gr.Button(interactive=True, value="Run Optimization Pipeline", variant="primary"),
256
- analyze_button: gr.Button(interactive=True, value="Analyze Model")
257
- }
258
  finally:
259
  if temp_model_dir and os.path.exists(temp_model_dir):
260
  shutil.rmtree(temp_model_dir)
261
- logging.info(f"Cleaned up temporary directory: {temp_model_dir}")
262
-
263
 
 
264
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
265
  gr.Markdown("# πŸš€ AMOP: Adaptive Model Optimization Pipeline")
266
- gr.Markdown("Turn any Hugging Face Hub model into a CPU-optimized version using ONNX or GGUF.")
267
-
268
- if not HF_TOKEN:
269
- gr.Warning("You have not set your HF_TOKEN in the Space secrets! The final 'upload' step will be skipped. Please add a secret with the key `HF_TOKEN` and your Hugging Face write token as the value.")
270
-
271
  with gr.Row():
272
  with gr.Column(scale=1):
273
  gr.Markdown("### 1. Select a Model")
274
- model_id_input = gr.Textbox(
275
- label="Hugging Face Model ID",
276
- placeholder="e.g., gpt2, google/gemma-2b",
277
- )
278
  analyze_button = gr.Button("πŸ” Analyze Model", variant="secondary")
279
-
280
  with gr.Accordion("βš™οΈ 2. Configure Optimization", open=False) as optimization_accordion:
281
  analysis_report_output = gr.Markdown()
282
-
283
- pipeline_type_radio = gr.Radio(
284
- ["ONNX", "GGUF"], label="Select Optimization Pipeline", info="GGUF is recommended for LLMs, ONNX for others."
285
- )
286
-
287
- # Unified Pruning controls, shown/hidden by parent group
288
- prune_checkbox = gr.Checkbox(label="Enable Pruning", value=False, info="Removes redundant weights from the model.")
289
- prune_slider = gr.Slider(minimum=0, maximum=90, value=20, step=5, label="Pruning Percentage (%)")
290
-
291
  with gr.Group(visible=False) as onnx_options:
292
- gr.Markdown("#### ONNX Quantization Options")
293
- onnx_quant_radio = gr.Radio(["Dynamic", "Static"], label="ONNX Quantization Type", value="Dynamic", info="Static may offer better performance but requires calibration data.")
294
  calibration_file_upload = gr.File(label="Upload Calibration Data (.txt)", visible=False, file_types=['.txt'])
295
-
296
  with gr.Group(visible=False) as gguf_options:
297
- gr.Markdown("#### GGUF Quantization Options")
298
- gguf_quant_dropdown = gr.Dropdown(
299
- ["q4_k_m", "q5_k_m", "q8_0", "f16"],
300
- label="GGUF Quantization Strategy",
301
- value="q4_k_m",
302
- info="q4_k_m is a good balance of size and quality."
303
- )
304
-
305
  run_button = gr.Button("πŸš€ Run Optimization Pipeline", variant="primary")
306
-
307
  with gr.Column(scale=2):
308
  gr.Markdown("### Pipeline Status & Logs")
309
- final_output = gr.Label(value="Idle", label="Status", show_label=True)
310
  success_box = gr.Markdown(visible=False)
311
- log_output = gr.Textbox(label="Live Logs", lines=20, interactive=False, max_lines=20)
312
 
313
  def update_ui_for_pipeline(pipeline_type):
314
- is_onnx = pipeline_type == "ONNX"
315
- is_gguf = pipeline_type == "GGUF"
316
- # Pruning controls are visible for either pipeline type, but grouped logically
317
- return {
318
- onnx_options: gr.Group(visible=is_onnx),
319
- gguf_options: gr.Group(visible=is_gguf),
320
- prune_checkbox: gr.Checkbox(visible=is_onnx or is_gguf),
321
- prune_slider: gr.Slider(visible=is_onnx or is_gguf)
322
- }
323
-
324
  def update_ui_for_quant_type(quant_type):
325
  return gr.File(visible=quant_type == "Static")
326
 
327
- pipeline_type_radio.change(fn=update_ui_for_pipeline, inputs=pipeline_type_radio, outputs=[onnx_options, gguf_options, prune_checkbox, prune_slider])
328
  onnx_quant_radio.change(fn=update_ui_for_quant_type, inputs=onnx_quant_radio, outputs=[calibration_file_upload])
329
-
330
- analyze_button.click(
331
- fn=stage_1_analyze_model,
332
- inputs=[model_id_input],
333
- outputs=[log_output, analysis_report_output, optimization_accordion]
334
- )
335
-
336
- run_button.click(
337
- fn=run_amop_pipeline,
338
- inputs=[
339
- model_id_input, pipeline_type_radio,
340
- prune_checkbox, prune_slider,
341
- onnx_quant_radio, calibration_file_upload, gguf_quant_dropdown
342
- ],
343
- outputs=[run_button, analyze_button, final_output, log_output, success_box]
344
- )
345
 
346
  if __name__ == "__main__":
347
  demo.launch(debug=True)
 
7
  import shutil
8
  import subprocess
9
  from datetime import datetime
10
+ from pathlib import Path
11
  from huggingface_hub import HfApi
12
  from transformers import AutoConfig, AutoModel, AutoTokenizer
13
  from optimum.onnxruntime import ORTQuantizer
14
  from optimum.onnxruntime.configuration import AutoQuantizationConfig
 
15
  import torch.nn.utils.prune as prune
16
 
17
+ # --- SETUP ---
18
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
19
 
20
  HF_TOKEN = os.getenv("HF_TOKEN")
 
25
  OUTPUT_DIR = "optimized_models"
26
  os.makedirs(OUTPUT_DIR, exist_ok=True)
27
 
28
+ # --- LLAMA.CPP SETUP ---
29
+ LLAMA_CPP_DIR = Path("llama.cpp")
30
+
31
+ def setup_llama_cpp():
32
+ """Clones llama.cpp if not already present."""
33
+ if not LLAMA_CPP_DIR.exists():
34
+ logging.info("Cloning llama.cpp repository...")
35
+ try:
36
+ subprocess.run(["git", "clone", "https://github.com/ggerganov/llama.cpp.git"], check=True, capture_output=True)
37
+ logging.info("llama.cpp cloned successfully.")
38
+ except subprocess.CalledProcessError as e:
39
+ error_msg = f"Failed to clone llama.cpp. This is required for GGUF conversion. Error: {e.stderr.decode()}"
40
+ logging.error(error_msg, exc_info=True)
41
+ raise RuntimeError(error_msg)
42
+
43
+ # Run setup on script start
44
+ try:
45
+ setup_llama_cpp()
46
+ LLAMA_CPP_CONVERT_SCRIPT = LLAMA_CPP_DIR / "convert.py"
47
+ # Note: llama.cpp's quantize script is also a python script now in many versions
48
+ LLAMA_CPP_QUANTIZE_SCRIPT = LLAMA_CPP_DIR / "quantize.py"
49
+ if not LLAMA_CPP_QUANTIZE_SCRIPT.exists(): # Fallback for older versions with compiled binary
50
+ LLAMA_CPP_QUANTIZE_SCRIPT = LLAMA_CPP_DIR / "quantize"
51
+ # Attempt to build if not found
52
+ if not LLAMA_CPP_QUANTIZE_SCRIPT.exists():
53
+ subprocess.run(["make", "-C", "llama.cpp", "quantize"], check=True, capture_output=True)
54
+
55
+ except Exception as e:
56
+ logging.error(f"FATAL ERROR during llama.cpp setup: {e}", exc_info=True)
57
+ # The app will likely fail to start, which is appropriate.
58
+
59
+
60
  def stage_1_analyze_model(model_id: str):
61
  log_stream = "[STAGE 1] Analyzing model...\n"
62
  try:
63
  config = AutoConfig.from_pretrained(model_id, trust_remote_code=True, token=HF_TOKEN)
64
  model_type = config.model_type
65
+ analysis_report = f"""### Model Analysis Report\n- **Model ID:** `{model_id}`\n- **Architecture:** `{model_type}`"""
 
 
 
 
 
 
66
  recommendation = ""
67
  if 'llama' in model_type or 'gpt' in model_type or 'mistral' in model_type or 'gemma' in model_type:
68
+ recommendation = "**Recommendation:** This is a Large Language Model (LLM). For the best CPU performance, the **GGUF Pipeline** (using llama.cpp) is highly recommended."
69
  else:
70
+ recommendation = "**Recommendation:** This is likely an encoder model. The **ONNX Pipeline** is recommended."
 
71
  log_stream += f"Analysis complete. Architecture: {model_type}.\n"
72
  return log_stream, analysis_report + "\n" + recommendation, gr.Accordion(open=True)
73
  except Exception as e:
74
  error_msg = f"Failed to analyze model '{model_id}'. Error: {e}"
75
  logging.error(error_msg)
76
+ return log_stream + error_msg, "Could not analyze model.", gr.Accordion(open=False)
77
 
78
  def stage_2_prune_model(model, prune_percentage: float):
79
  if prune_percentage == 0:
 
93
  onnx_path = os.path.join(OUTPUT_DIR, f"{model_name}-{run_id}-onnx")
94
 
95
  try:
96
+ log_stream += "Executing `optimum-cli export onnx`...\n"
97
+ export_command = ["optimum-cli", "export", "onnx", "--model", model_path, "--trust-remote-code", onnx_path]
 
 
 
 
 
98
  process = subprocess.run(export_command, check=True, capture_output=True, text=True)
99
  log_stream += process.stdout
100
  if process.stderr: log_stream += f"[STDERR]\n{process.stderr}\n"
101
+ log_stream += f"Successfully exported to ONNX at: {onnx_path}\n"
102
  except subprocess.CalledProcessError as e:
103
+ raise RuntimeError(f"Failed during `optimum-cli export onnx`. Error:\n{e.stderr}")
 
 
104
 
105
  try:
106
  quantizer = ORTQuantizer.from_pretrained(onnx_path)
 
107
  if calibration_data_path:
108
+ log_stream += "Performing STATIC quantization...\n"
109
  dqconfig = AutoQuantizationConfig.avx512_vnni(is_static=True, per_channel=False)
 
 
 
 
 
 
 
110
  quantized_path = os.path.join(onnx_path, "quantized-static")
111
+ quantizer.quantize(save_dir=quantized_path, quantization_config=dqconfig, calibration_dataset=quantizer.get_calibration_dataset("text", dataset_args={"path": calibration_data_path, "split": "train"}, num_samples=100))
112
  else:
113
+ log_stream += "Performing DYNAMIC quantization...\n"
114
  dqconfig = AutoQuantizationConfig.avx512_vnni(is_static=False, per_channel=False)
115
  quantized_path = os.path.join(onnx_path, "quantized-dynamic")
116
  quantizer.quantize(save_dir=quantized_path, quantization_config=dqconfig)
 
117
  log_stream += f"Successfully quantized model to: {quantized_path}\n"
118
  return quantized_path, log_stream
119
  except Exception as e:
120
+ raise RuntimeError(f"Failed during ONNX quantization step. Error: {e}")
 
 
121
 
122
  def stage_3_4_gguf_quantize(model_path: str, model_id: str, quantization_strategy: str):
123
+ log_stream = "[STAGE 3 & 4] Converting to GGUF using llama.cpp...\n"
124
  run_id = datetime.now().strftime("%Y%m%d-%H%M%S")
125
  model_name = model_id.replace('/', '_')
126
  gguf_path = os.path.join(OUTPUT_DIR, f"{model_name}-{run_id}-gguf")
127
  os.makedirs(gguf_path, exist_ok=True)
128
+
129
+ f16_gguf_path = os.path.join(gguf_path, "model-f16.gguf")
130
+ quantized_gguf_path = os.path.join(gguf_path, "model.gguf")
131
 
132
  try:
133
+ log_stream += "Executing llama.cpp convert.py script...\n"
134
+ convert_command = ["python", str(LLAMA_CPP_CONVERT_SCRIPT), model_path, "--outfile", f16_gguf_path, "--outtype", "f16"]
135
+ process = subprocess.run(convert_command, check=True, capture_output=True, text=True)
136
+ log_stream += process.stdout
137
+ if process.stderr: log_stream += f"[STDERR]\n{process.stderr}\n"
138
+
139
+ quantize_map = {"q4_k_m": "Q4_K_M", "q5_k_m": "Q5_K_M", "q8_0": "Q8_0", "f16": "F16"}
140
+ target_quant_name = quantize_map.get(quantization_strategy.lower(), "Q4_K_M")
141
+
142
+ if target_quant_name == "F16":
143
+ log_stream += "Target is F16, renaming file...\n"
144
+ os.rename(f16_gguf_path, quantized_gguf_path)
145
+ else:
146
+ log_stream += f"Quantizing FP16 GGUF to {target_quant_name}...\n"
147
+ quantize_cmd_base = [str(LLAMA_CPP_QUANTIZE_SCRIPT)] if LLAMA_CPP_QUANTIZE_SCRIPT.is_file() and os.access(LLAMA_CPP_QUANTIZE_SCRIPT, os.X_OK) else ["python", str(LLAMA_CPP_QUANTIZE_SCRIPT)]
148
+ quantize_command = quantize_cmd_base + [f16_gguf_path, quantized_gguf_path, target_quant_name]
149
+ process = subprocess.run(quantize_command, check=True, capture_output=True, text=True)
150
+ log_stream += process.stdout
151
+ if process.stderr: log_stream += f"[STDERR]\n{process.stderr}\n"
152
+ os.remove(f16_gguf_path)
153
  return gguf_path, log_stream
154
+ except subprocess.CalledProcessError as e:
155
+ raise RuntimeError(f"Failed during llama.cpp execution. Error:\n{e.stderr}")
156
  except Exception as e:
157
+ raise RuntimeError(f"An unexpected error occurred during GGUF conversion. Error: {e}")
 
 
 
158
 
159
  def stage_5_package_and_upload(model_id: str, optimized_model_path: str, pipeline_log: str, options: dict):
160
+ # This function remains correct and does not need changes
161
  log_stream = "[STAGE 5] Packaging and Uploading...\n"
162
  if not HF_TOKEN:
163
  return "Skipping upload: HF_TOKEN not found.", log_stream + "Skipping upload: HF_TOKEN not found."
 
164
  try:
165
  repo_name = f"{model_id.split('/')[-1]}-amop-cpu-{options['pipeline_type'].lower()}"
166
  repo_url = api.create_repo(repo_id=repo_name, exist_ok=True, token=HF_TOKEN)
167
+ template_file = "model_card_template_gguf.md" if options['pipeline_type'] == "GGUF" else "model_card_template.md"
168
+ with open(template_file, "r", encoding="utf-8") as f: template_content = f.read()
169
+ model_card_content = template_content.format(repo_name=repo_name, model_id=model_id, optimization_date=datetime.now().strftime("%Y-%m-%d %H:%M:%S"), pruning_status="Enabled" if options.get('prune', False) else "Disabled", pruning_percent=options.get('prune_percent', 0), quant_type=options.get('quant_type', 'N/A'), repo_id=repo_url.repo_id, pipeline_log=pipeline_log)
170
+ with open(os.path.join(optimized_model_path, "README.md"), "w", encoding="utf-8") as f: f.write(model_card_content)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  if options['pipeline_type'] == "ONNX":
172
+ AutoTokenizer.from_pretrained(model_id, trust_remote_code=True).save_pretrained(optimized_model_path)
 
 
173
  api.upload_folder(folder_path=optimized_model_path, repo_id=repo_url.repo_id, repo_type="model", token=HF_TOKEN)
 
 
174
  log_stream += "Upload complete.\n"
175
+ return f"Success! Your optimized model is available at: huggingface.co/{repo_url.repo_id}", log_stream
176
  except Exception as e:
177
+ raise RuntimeError(f"Failed to upload to the Hub. Error: {e}")
 
 
178
 
179
  def run_amop_pipeline(model_id: str, pipeline_type: str, do_prune: bool, prune_percent: float, onnx_quant_type: str, calibration_file, gguf_quant_type: str):
180
  if not model_id:
 
182
  return
183
 
184
  initial_log = f"[START] AMOP {pipeline_type} Pipeline Initiated.\n"
185
+ yield {run_button: gr.Button(interactive=False, value="πŸš€ Running..."), analyze_button: gr.Button(interactive=False), final_output: f"RUNNING ({pipeline_type})", log_output: initial_log}
186
+
 
 
 
 
 
187
  full_log = initial_log
188
  temp_model_dir = None
189
  try:
 
190
  whoami = api.whoami()
191
+ if not whoami: raise RuntimeError("Could not authenticate with Hugging Face Hub. Check your HF_TOKEN.")
192
+ repo_id_for_link = f"{whoami['name']}/{model_id.split('/')[-1]}-amop-cpu-{pipeline_type.lower()}"
 
193
 
194
+ full_log += "Loading base model...\n"; yield {final_output: "Loading model (1/5)", log_output: full_log}
 
195
  model = AutoModel.from_pretrained(model_id, trust_remote_code=True)
196
  tokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)
197
+ full_log += f"Successfully loaded '{model_id}'.\n"
 
 
 
 
 
 
 
198
 
199
+ full_log += "Pruning model...\n"; yield {final_output: "Pruning model (2/5)", log_output: full_log}
200
+ model, log = stage_2_prune_model(model, prune_percent if do_prune else 0)
201
+ full_log += log
202
+
203
  temp_model_dir = tempfile.mkdtemp()
204
  model.save_pretrained(temp_model_dir)
205
  tokenizer.save_pretrained(temp_model_dir)
206
+ full_log += f"Saved intermediate model to {temp_model_dir}\n"
207
 
208
  if pipeline_type == "ONNX":
209
+ full_log += "Converting to ONNX...\n"; yield {final_output: "Converting to ONNX (3/5)", log_output: full_log}
210
+ optimized_path, log = stage_3_4_onnx_quantize(temp_model_dir, calibration_file.name if onnx_quant_type == "Static" and calibration_file else None)
 
 
211
  options = {'pipeline_type': 'ONNX', 'prune': do_prune, 'prune_percent': prune_percent, 'quant_type': onnx_quant_type}
 
212
  elif pipeline_type == "GGUF":
213
+ full_log += "Converting to GGUF...\n"; yield {final_output: "Converting to GGUF (3/5)", log_output: full_log}
214
  optimized_path, log = stage_3_4_gguf_quantize(temp_model_dir, model_id, gguf_quant_type)
 
215
  options = {'pipeline_type': 'GGUF', 'prune': do_prune, 'prune_percent': prune_percent, 'quant_type': gguf_quant_type}
 
216
  else:
217
  raise ValueError("Invalid pipeline type selected.")
218
+ full_log += log
219
+
220
+ full_log += "Packaging & Uploading...\n"; yield {final_output: "Packaging & Uploading (4/5)", log_output: full_log}
221
  final_message, log = stage_5_package_and_upload(model_id, optimized_path, full_log, options)
222
  full_log += log
223
 
224
+ yield {final_output: gr.update(value="SUCCESS", label="Status"), log_output: full_log, success_box: gr.Markdown(f"βœ… **Success!** Model available: [{repo_id_for_link}](https://huggingface.co/{repo_id_for_link})", visible=True), run_button: gr.Button(interactive=True, value="Run Optimization Pipeline", variant="primary"), analyze_button: gr.Button(interactive=True, value="Analyze Model")}
 
 
 
 
 
 
 
225
  except Exception as e:
226
  logging.error(f"AMOP Pipeline failed. Error: {e}", exc_info=True)
227
  full_log += f"\n[ERROR] Pipeline failed: {e}"
228
+ yield {final_output: gr.update(value="ERROR", label="Status"), log_output: full_log, success_box: gr.Markdown(f"❌ **An error occurred.** Check logs for details.", visible=True), run_button: gr.Button(interactive=True, value="Run Optimization Pipeline", variant="primary"), analyze_button: gr.Button(interactive=True, value="Analyze Model")}
 
 
 
 
 
 
229
  finally:
230
  if temp_model_dir and os.path.exists(temp_model_dir):
231
  shutil.rmtree(temp_model_dir)
 
 
232
 
233
+ # --- GRADIO UI ---
234
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
235
  gr.Markdown("# πŸš€ AMOP: Adaptive Model Optimization Pipeline")
236
+ if not HF_TOKEN: gr.Warning("HF_TOKEN not set! The final 'upload' step will be skipped.")
 
 
 
 
237
  with gr.Row():
238
  with gr.Column(scale=1):
239
  gr.Markdown("### 1. Select a Model")
240
+ model_id_input = gr.Textbox(label="Hugging Face Model ID", placeholder="e.g., gpt2, google/gemma-2b")
 
 
 
241
  analyze_button = gr.Button("πŸ” Analyze Model", variant="secondary")
 
242
  with gr.Accordion("βš™οΈ 2. Configure Optimization", open=False) as optimization_accordion:
243
  analysis_report_output = gr.Markdown()
244
+ pipeline_type_radio = gr.Radio(["ONNX", "GGUF"], label="Select Optimization Pipeline")
245
+ prune_checkbox = gr.Checkbox(label="Enable Pruning", value=False, info="Removes redundant weights.", visible=True)
246
+ prune_slider = gr.Slider(minimum=0, maximum=90, value=20, step=5, label="Pruning Percentage (%)", visible=True)
 
 
 
 
 
 
247
  with gr.Group(visible=False) as onnx_options:
248
+ gr.Markdown("#### ONNX Options")
249
+ onnx_quant_radio = gr.Radio(["Dynamic", "Static"], label="Quantization Type", value="Dynamic")
250
  calibration_file_upload = gr.File(label="Upload Calibration Data (.txt)", visible=False, file_types=['.txt'])
 
251
  with gr.Group(visible=False) as gguf_options:
252
+ gr.Markdown("#### GGUF Options")
253
+ gguf_quant_dropdown = gr.Dropdown(["q4_k_m", "q5_k_m", "q8_0", "f16"], label="Quantization Strategy", value="q4_k_m")
 
 
 
 
 
 
254
  run_button = gr.Button("πŸš€ Run Optimization Pipeline", variant="primary")
 
255
  with gr.Column(scale=2):
256
  gr.Markdown("### Pipeline Status & Logs")
257
+ final_output = gr.Label(value="Idle", label="Status")
258
  success_box = gr.Markdown(visible=False)
259
+ log_output = gr.Textbox(label="Live Logs", lines=20, interactive=False)
260
 
261
  def update_ui_for_pipeline(pipeline_type):
262
+ return {onnx_options: gr.Group(visible=pipeline_type=="ONNX"), gguf_options: gr.Group(visible=pipeline_type=="GGUF")}
 
 
 
 
 
 
 
 
 
263
  def update_ui_for_quant_type(quant_type):
264
  return gr.File(visible=quant_type == "Static")
265
 
266
+ pipeline_type_radio.change(fn=update_ui_for_pipeline, inputs=pipeline_type_radio, outputs=[onnx_options, gguf_options])
267
  onnx_quant_radio.change(fn=update_ui_for_quant_type, inputs=onnx_quant_radio, outputs=[calibration_file_upload])
268
+ analyze_button.click(fn=stage_1_analyze_model, inputs=[model_id_input], outputs=[log_output, analysis_report_output, optimization_accordion])
269
+ run_button.click(fn=run_amop_pipeline, inputs=[model_id_input, pipeline_type_radio, prune_checkbox, prune_slider, onnx_quant_radio, calibration_file_upload, gguf_quant_dropdown], outputs=[run_button, analyze_button, final_output, log_output, success_box])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
270
 
271
  if __name__ == "__main__":
272
  demo.launch(debug=True)