Shanmuk4622 commited on
Commit
c43a0be
Β·
verified Β·
1 Parent(s): b8d7c61

Upload eden_chart_push.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. eden_chart_push.py +881 -0
eden_chart_push.py ADDED
@@ -0,0 +1,881 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Project EDEN β€” Chart Generation & Model Card Enhancement Script
3
+ Generates training visualizations for every model card and pushes to HuggingFace.
4
+
5
+ Charts generated:
6
+ Collection (EDEN-Core-Scripts repo):
7
+ - energy_accuracy_overview.png : all models, energy vs accuracy scatter
8
+ - eag_leaderboard.png : all models ranked by EAG
9
+ - co2_comparison.png : CO2 baseline vs EDEN per architecture
10
+
11
+ Per-model repo:
12
+ - training_curve.png : accuracy + cumulative energy vs epoch
13
+ - eag_curve.png : EAG metric trajectory over epochs
14
+
15
+ Model cards also get:
16
+ - model-index YAML (enables HF native metrics widget)
17
+ - Embedded chart images in README
18
+ """
19
+
20
+ import os, json, glob
21
+ import pandas as pd
22
+ import matplotlib
23
+ matplotlib.use('Agg')
24
+ import matplotlib.pyplot as plt
25
+ import matplotlib.patches as mpatches
26
+ import numpy as np
27
+ from huggingface_hub import HfApi, create_repo, upload_file
28
+
29
+ # ─── CONFIG ──────────────────────────────────────────────────────────────────
30
+ HF_TOKEN = os.environ.get("HF_TOKEN", "")
31
+ HF_USER = "Shanmuk4622"
32
+ HF_ORG = HF_USER
33
+ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
34
+ CHARTS_DIR = os.path.join(BASE_DIR, "charts")
35
+ HF_READMES_DIR = os.path.join(BASE_DIR, "hf_readmes")
36
+ os.makedirs(CHARTS_DIR, exist_ok=True)
37
+ os.makedirs(HF_READMES_DIR, exist_ok=True)
38
+
39
+ api = HfApi(token=HF_TOKEN)
40
+
41
+ # ─── DARK THEME PALETTE ──────────────────────────────────────────────────────
42
+ DARK_BG = "#0d1117"
43
+ CARD_BG = "#161b22"
44
+ GRID_COLOR = "#21262d"
45
+ TEXT_COLOR = "#e6edf3"
46
+ GREEN = "#2ea043"
47
+ GREEN_LT = "#56d364"
48
+ ORANGE = "#f0883e"
49
+ BLUE = "#58a6ff"
50
+ PURPLE = "#bc8cff"
51
+ RED = "#f85149"
52
+ MUTED = "#8b949e"
53
+
54
+ # ─── STATIC METADATA (mirrors eden_hf_upload.py) ─────────────────────────────
55
+ HARDWARE = {
56
+ "gpu": "NVIDIA GeForce GTX 1080 Ti (11 GB VRAM, 250 W TDP)",
57
+ "cpu": "Intel Xeon W-2125 (4 cores / 8 threads @ 4.00 GHz)",
58
+ "ram": "63.66 GB System RAM",
59
+ "os": "Windows 10",
60
+ }
61
+
62
+ PHASE_MAP = {
63
+ "test1": "Phase 2 – Progressive Unfreezing + AMP (E2AM SOTA)",
64
+ "test2": "Baseline – Standard Full Training (Reference Study)",
65
+ "test3": "Phase 2 – EDEN Classic Energy-Aware Sparse Training",
66
+ }
67
+
68
+ PHASE_DETAIL = {
69
+ "test1": (
70
+ "**Phase 1 – Zero-Overhead Initialization:** Dataset pre-loaded into pinned "
71
+ "System RAM to eliminate disk I/O power spikes.\n\n"
72
+ "**Phase 2 – Progressive Unfreezing:** Backbone frozen for the first "
73
+ "`E_unfreeze` epochs (only the classification head trains). At `E_unfreeze`, "
74
+ "all layers are unfrozen and the learning rate is decayed. "
75
+ "Gradient accumulation over N micro-batches simulates large batch sizes "
76
+ "without proportional VRAM cost, slashing power-draw spikes.\n\n"
77
+ "**AMP (Automated Mixed Precision):** `torch.cuda.amp.autocast()` halves "
78
+ "GPU memory bandwidth, reducing energy per backward pass.\n\n"
79
+ "**Sparse Regularisation:** L1 penalty `λ·Σ|W|` applied to trainable "
80
+ "weights, driving dead neurons to zero and enabling future pruning."
81
+ ),
82
+ "test2": (
83
+ "Standard full fine-tuning used as the **Brute-Force Baseline** for "
84
+ "energy comparison. All layers trained from epoch 1 with a fixed learning "
85
+ "rate and no gradient accumulation. Included for transparent EAG benchmarking."
86
+ ),
87
+ "test3": (
88
+ "**Phase 1 – Zero-Overhead Initialization:** Dataset cached in System RAM.\n\n"
89
+ "**Phase 2 – EDEN Classic:** Energy-aware training loop on classic CNN "
90
+ "architectures. Applies the same EAG early-exit criterion "
91
+ "(`EAG < Ξ³_EAG` for 3 consecutive epochs β†’ terminate), L1 sparsity "
92
+ "penalty, and AMP to architectures like ResNet, VGG, AlexNet, DenseNet, "
93
+ "InceptionV3, and UNet."
94
+ ),
95
+ }
96
+
97
+ DATASET_META = {
98
+ "CIFAR-10": {"size": "60,000 images – 10 classes (32Γ—32 px)", "hf_name": "cifar10"},
99
+ "CIFAR-100": {"size": "60,000 images – 100 classes (32Γ—32 px)", "hf_name": "cifar100"},
100
+ "Custom-ImageNet300": {"size": "~450,000 images – 300 classes (224 px)", "hf_name": "imagenet"},
101
+ "unknown": {"size": "N/A", "hf_name": "unknown"},
102
+ }
103
+
104
+ # ─── HELPERS ─────────────────────────────────────────────────────────────────
105
+ def parse_name(filename):
106
+ fn = filename.lower().replace("\\", "/")
107
+ dataset, arch = "unknown", "unknown"
108
+ if "cifar100" in fn: dataset = "CIFAR-100"
109
+ elif "cifar10" in fn: dataset = "CIFAR-10"
110
+ elif "imagenet" in fn: dataset = "Custom-ImageNet300"
111
+ if "efficientnet" in fn: arch = "EfficientNetV2"
112
+ elif "convnext" in fn: arch = "ConvNeXtV2"
113
+ elif "mobilevit" in fn: arch = "MobileViTv3"
114
+ elif "resnet50" in fn: arch = "ResNet50"
115
+ elif "resnet18" in fn: arch = "ResNet18"
116
+ elif "vgg16" in fn: arch = "VGG16"
117
+ elif "alexnet" in fn: arch = "AlexNet"
118
+ elif "inception" in fn: arch = "InceptionV3"
119
+ elif "densenet" in fn: arch = "DenseNet121"
120
+ elif "unet" in fn: arch = "UNet"
121
+ return arch, dataset
122
+
123
+ def folder_to_phase(folder):
124
+ return {"test1": "SOTA Optimized", "test2": "Baseline", "test3": "EDEN Classic"}.get(folder, folder)
125
+
126
+ def co2_kg(joules):
127
+ return (joules / 3_600_000) * 0.475 if joules else 0
128
+
129
+ def setup_dark_style():
130
+ plt.rcParams.update({
131
+ 'figure.facecolor': DARK_BG,
132
+ 'axes.facecolor': CARD_BG,
133
+ 'axes.edgecolor': GRID_COLOR,
134
+ 'axes.labelcolor': TEXT_COLOR,
135
+ 'axes.titlecolor': TEXT_COLOR,
136
+ 'xtick.color': MUTED,
137
+ 'ytick.color': MUTED,
138
+ 'grid.color': GRID_COLOR,
139
+ 'grid.alpha': 0.7,
140
+ 'text.color': TEXT_COLOR,
141
+ 'font.family': 'DejaVu Sans',
142
+ 'font.size': 11,
143
+ 'axes.spines.top': False,
144
+ 'axes.spines.right': False,
145
+ })
146
+
147
+ def load_csv(csv_path):
148
+ """Load training CSV and normalize column names across test1/2/3."""
149
+ try:
150
+ df = pd.read_csv(csv_path)
151
+ df.rename(columns={
152
+ 'total_energy_j': 'epoch_total_energy_j',
153
+ 'energy_gpu_j': 'epoch_energy_gpu_j',
154
+ 'carbon_kg': 'carbon_emissions_kg',
155
+ 'vram_gb': 'vram_peak_gb',
156
+ 'grad_norm': 'avg_grad_norm',
157
+ }, inplace=True)
158
+ return df
159
+ except Exception as e:
160
+ print(f" Warning: could not load {csv_path}: {e}")
161
+ return None
162
+
163
+ # ─── LOAD RESULTS ────────────────────────────────────────────────────────────
164
+ with open(os.path.join(BASE_DIR, "results_summary.json")) as f:
165
+ results = json.load(f)
166
+
167
+ stats_map = {}
168
+ for r in results:
169
+ arch, dataset = parse_name(r["file"])
170
+ key = f"{r['folder']}_{arch}_{dataset}"
171
+ if key not in stats_map or (r["energy"] > 0 and stats_map[key]["energy"] == 0):
172
+ stats_map[key] = r
173
+
174
+ # Baseline per dataset (prefer ResNet50 from test2)
175
+ baselines = {}
176
+ for key, v in stats_map.items():
177
+ folder = key.split("_")[0]
178
+ if folder != "test2": continue
179
+ _, ds = parse_name(v["file"])
180
+ if ds not in baselines:
181
+ baselines[ds] = v
182
+ if parse_name(v["file"])[0] == "ResNet50":
183
+ baselines[ds] = v
184
+
185
+ # Collect all .pth files β†’ deduplicate by repo name (highest accuracy wins)
186
+ pth_files = glob.glob(os.path.join(BASE_DIR, "**/*.pth"), recursive=True)
187
+ models_raw = []
188
+ for pth in pth_files:
189
+ rel = os.path.relpath(pth, BASE_DIR)
190
+ folder = rel.split(os.sep)[0]
191
+ arch, dataset = parse_name(rel)
192
+ key = f"{folder}_{arch}_{dataset}"
193
+ stat = stats_map.get(key, {})
194
+ models_raw.append({
195
+ "pth": rel, "arch": arch, "dataset": dataset, "folder": folder,
196
+ "accuracy": stat.get("accuracy", 0),
197
+ "energy": stat.get("energy", 0),
198
+ "time": stat.get("time", 0),
199
+ "csv": stat.get("file", "N/A"),
200
+ })
201
+
202
+ repo_model_map = {}
203
+ for m in models_raw:
204
+ if m["arch"] == "unknown" or m["dataset"] == "unknown": continue
205
+ repo_name = f"EDEN-{m['arch']}-{m['dataset'].replace(' ', '-')}"
206
+ if repo_name not in repo_model_map or m["accuracy"] > repo_model_map[repo_name]["accuracy"]:
207
+ repo_model_map[repo_name] = m
208
+
209
+ # ═══════════════════════════════════════════════════════════════════════════════
210
+ # PER-MODEL CHARTS
211
+ # ═══════════════════════════════════════════════════════════════════════════════
212
+
213
+ def generate_training_curve(model, out_path):
214
+ """Dual-axis: Accuracy % (green) + Cumulative Energy MJ (orange) vs Epoch."""
215
+ if model["csv"] == "N/A": return False
216
+ csv_path = os.path.join(BASE_DIR, model["csv"])
217
+ if not os.path.exists(csv_path): return False
218
+ df = load_csv(csv_path)
219
+ if df is None or len(df) < 2 or "cumulative_total_energy_j" not in df.columns:
220
+ return False
221
+
222
+ setup_dark_style()
223
+ fig, ax1 = plt.subplots(figsize=(11, 5.5))
224
+ fig.patch.set_facecolor(DARK_BG)
225
+ ax1.set_facecolor(CARD_BG)
226
+
227
+ epochs = df["epoch"]
228
+ acc_pct = df["accuracy"] * 100
229
+ energy_mj = df["cumulative_total_energy_j"] / 1_000_000
230
+
231
+ # Shade FROZEN phase (test3)
232
+ if "status" in df.columns:
233
+ frozen_mask = df["status"] == "FROZEN"
234
+ if frozen_mask.any():
235
+ f_min = df.loc[frozen_mask, "epoch"].min()
236
+ f_max = df.loc[frozen_mask, "epoch"].max()
237
+ ax1.axvspan(f_min, f_max, alpha=0.08, color=BLUE, label="Frozen phase")
238
+ ax1.axvline(f_max + 0.5, color=BLUE, linewidth=1, linestyle=':', alpha=0.6)
239
+ ax1.text(f_max + 0.7, acc_pct.min(), "Unfreeze β†’",
240
+ color=BLUE, fontsize=9, va='bottom', alpha=0.8)
241
+
242
+ # Accuracy
243
+ ax1.plot(epochs, acc_pct, color=GREEN_LT, linewidth=2.5, label="Accuracy (%)", zorder=3)
244
+ ax1.fill_between(epochs, acc_pct, alpha=0.10, color=GREEN_LT)
245
+ ax1.set_xlabel("Epoch", fontsize=12)
246
+ ax1.set_ylabel("Accuracy (%)", color=GREEN_LT, fontsize=12)
247
+ ax1.tick_params(axis='y', labelcolor=GREEN_LT)
248
+ ax1.set_ylim(bottom=max(0, float(acc_pct.min()) * 0.93))
249
+ ax1.grid(True, linestyle='--', alpha=0.35)
250
+
251
+ # Final accuracy annotation
252
+ ax1.annotate(
253
+ f"{float(acc_pct.iloc[-1]):.2f}%",
254
+ xy=(float(epochs.iloc[-1]), float(acc_pct.iloc[-1])),
255
+ xytext=(-45, 12), textcoords='offset points',
256
+ color=GREEN_LT, fontsize=10, fontweight='bold',
257
+ arrowprops=dict(arrowstyle='->', color=GREEN_LT, lw=1.5)
258
+ )
259
+
260
+ # Energy (right axis)
261
+ ax2 = ax1.twinx()
262
+ ax2.set_facecolor(CARD_BG)
263
+ ax2.plot(epochs, energy_mj, color=ORANGE, linewidth=2, linestyle='--',
264
+ label="Cumulative Energy (MJ)", zorder=2, alpha=0.9)
265
+ ax2.set_ylabel("Cumulative Energy (MJ)", color=ORANGE, fontsize=12)
266
+ ax2.tick_params(axis='y', labelcolor=ORANGE)
267
+ for sp in ax2.spines.values(): sp.set_color(GRID_COLOR)
268
+
269
+ for sp in ax1.spines.values(): sp.set_color(GRID_COLOR)
270
+
271
+ phase = folder_to_phase(model["folder"])
272
+ ax1.set_title(
273
+ f"Training Curve Β· {model['arch']} on {model['dataset']} [{phase}]",
274
+ fontsize=13, fontweight='bold', pad=14
275
+ )
276
+
277
+ lines1, lab1 = ax1.get_legend_handles_labels()
278
+ lines2, lab2 = ax2.get_legend_handles_labels()
279
+ ax1.legend(lines1 + lines2, lab1 + lab2,
280
+ loc='lower right', facecolor=CARD_BG, edgecolor=GRID_COLOR,
281
+ labelcolor=TEXT_COLOR, fontsize=10)
282
+
283
+ plt.tight_layout()
284
+ plt.savefig(out_path, dpi=150, bbox_inches='tight', facecolor=DARK_BG)
285
+ plt.close()
286
+ return True
287
+
288
+
289
+ def generate_eag_curve(model, out_path):
290
+ """EAG metric trajectory, with frozen-phase shading for test3."""
291
+ if model["csv"] == "N/A": return False
292
+ csv_path = os.path.join(BASE_DIR, model["csv"])
293
+ if not os.path.exists(csv_path): return False
294
+ df = load_csv(csv_path)
295
+ if df is None or "eag_metric" not in df.columns or len(df) < 2:
296
+ return False
297
+
298
+ eag_series = df["eag_metric"].replace(0, np.nan)
299
+ valid = eag_series.dropna()
300
+ if len(valid) < 2: return False
301
+
302
+ setup_dark_style()
303
+ fig, ax = plt.subplots(figsize=(11, 4.5))
304
+ fig.patch.set_facecolor(DARK_BG)
305
+ ax.set_facecolor(CARD_BG)
306
+
307
+ valid_epochs = df.loc[eag_series.notna(), "epoch"]
308
+
309
+ ax.plot(valid_epochs, valid, color=BLUE, linewidth=2.5, zorder=3)
310
+ ax.fill_between(valid_epochs, valid, alpha=0.12, color=BLUE)
311
+
312
+ # Zero-line reference
313
+ ax.axhline(0, color=MUTED, linewidth=0.8, linestyle='-', alpha=0.5)
314
+
315
+ # Shade FROZEN phase
316
+ if "status" in df.columns:
317
+ frozen_mask = df["status"] == "FROZEN"
318
+ if frozen_mask.any():
319
+ f_min = df.loc[frozen_mask, "epoch"].min()
320
+ f_max = df.loc[frozen_mask, "epoch"].max()
321
+ ax.axvspan(f_min, f_max, alpha=0.08, color=PURPLE)
322
+ ax.text((f_min + f_max) / 2, float(valid.max()) * 0.9,
323
+ "Frozen", color=PURPLE, fontsize=9, ha='center', alpha=0.8)
324
+
325
+ ax.set_xlabel("Epoch", fontsize=12)
326
+ ax.set_ylabel("EAG (Ξ”Acc / Ξ”Joules)", color=BLUE, fontsize=12)
327
+ ax.tick_params(axis='y', labelcolor=BLUE)
328
+ ax.ticklabel_format(axis='y', style='sci', scilimits=(0, 0))
329
+ ax.grid(True, linestyle='--', alpha=0.35)
330
+ for sp in ax.spines.values(): sp.set_color(GRID_COLOR)
331
+
332
+ phase = folder_to_phase(model["folder"])
333
+ ax.set_title(
334
+ f"EAG Trajectory Β· {model['arch']} on {model['dataset']} [{phase}]",
335
+ fontsize=13, fontweight='bold', pad=14
336
+ )
337
+ ax.text(0.01, 0.02,
338
+ "Higher EAG = more accuracy gained per Joule consumed",
339
+ transform=ax.transAxes, color=MUTED, fontsize=9, va='bottom')
340
+
341
+ plt.tight_layout()
342
+ plt.savefig(out_path, dpi=150, bbox_inches='tight', facecolor=DARK_BG)
343
+ plt.close()
344
+ return True
345
+
346
+ # ═══════════════════════════════════════════════════════════════════════════════
347
+ # COLLECTION CHARTS
348
+ # ═══════════════════════════════════���═══════════════════════════════════════════
349
+
350
+ def generate_energy_accuracy_overview(out_path):
351
+ """Scatter: all models by energy (MJ) vs accuracy (%), colored by phase."""
352
+ setup_dark_style()
353
+ fig, ax = plt.subplots(figsize=(13, 7))
354
+ fig.patch.set_facecolor(DARK_BG)
355
+ ax.set_facecolor(CARD_BG)
356
+
357
+ phase_color = {"test1": GREEN_LT, "test2": MUTED, "test3": BLUE}
358
+
359
+ for repo_name, m in repo_model_map.items():
360
+ if m["energy"] == 0 or m["accuracy"] == 0: continue
361
+ color = phase_color.get(m["folder"], MUTED)
362
+ e_mj = m["energy"] / 1_000_000
363
+ acc = m["accuracy"] * 100
364
+ ax.scatter(e_mj, acc, s=130, color=color, alpha=0.85, zorder=3,
365
+ edgecolors='white', linewidths=0.5)
366
+ label = f"{m['arch']}\n{m['dataset']}"
367
+ ax.annotate(label, (e_mj, acc), textcoords='offset points',
368
+ xytext=(6, 4), fontsize=7.5, color=TEXT_COLOR, alpha=0.8)
369
+
370
+ legend_handles = [
371
+ mpatches.Patch(color=GREEN_LT, label="SOTA Optimized (E2AM Phase 2)"),
372
+ mpatches.Patch(color=MUTED, label="Baseline (standard training)"),
373
+ mpatches.Patch(color=BLUE, label="EDEN Classic (energy-aware CNNs)"),
374
+ ]
375
+ ax.legend(handles=legend_handles, facecolor=CARD_BG, edgecolor=GRID_COLOR,
376
+ labelcolor=TEXT_COLOR, fontsize=10, loc='lower right')
377
+
378
+ ax.set_xlabel("Total Training Energy (MJ)", fontsize=12)
379
+ ax.set_ylabel("Final Accuracy (%)", fontsize=12)
380
+ ax.grid(True, linestyle='--', alpha=0.3)
381
+ for sp in ax.spines.values(): sp.set_color(GRID_COLOR)
382
+
383
+ ax.set_title("Project EDEN β€” Energy vs Accuracy (all models)",
384
+ fontsize=14, fontweight='bold', pad=14)
385
+ fig.text(0.5, 0.005,
386
+ "↙ lower energy + higher accuracy = better Green SOTA",
387
+ ha='center', color=MUTED, fontsize=10)
388
+
389
+ plt.tight_layout(rect=[0, 0.03, 1, 1])
390
+ plt.savefig(out_path, dpi=150, bbox_inches='tight', facecolor=DARK_BG)
391
+ plt.close()
392
+ print(" βœ“ energy_accuracy_overview.png")
393
+
394
+
395
+ def generate_eag_leaderboard(out_path):
396
+ """Horizontal bar chart: all models ranked by EAG score."""
397
+ setup_dark_style()
398
+
399
+ entries = []
400
+ for repo_name, m in repo_model_map.items():
401
+ b = baselines.get(m["dataset"], {})
402
+ b_e, b_a = b.get("energy", 0), b.get("accuracy", 0)
403
+ if b_e and m["energy"] and b_e != m["energy"]:
404
+ d_j = m["energy"] - b_e
405
+ eag = (m["accuracy"] - b_a) / d_j
406
+ short = repo_name.replace("EDEN-", "")
407
+ entries.append((short, eag, m["folder"]))
408
+
409
+ if not entries: return
410
+ entries.sort(key=lambda x: x[1])
411
+
412
+ names = [e[0] for e in entries]
413
+ eags = [e[1] for e in entries]
414
+ colors = [GREEN_LT if v >= 0 else RED for v in eags]
415
+
416
+ fig, ax = plt.subplots(figsize=(12, max(6, len(names) * 0.44)))
417
+ fig.patch.set_facecolor(DARK_BG)
418
+ ax.set_facecolor(CARD_BG)
419
+
420
+ bars = ax.barh(names, eags, color=colors, alpha=0.85,
421
+ edgecolor=GRID_COLOR, linewidth=0.5, height=0.7)
422
+ ax.axvline(0, color=MUTED, linewidth=1)
423
+
424
+ rng = max(abs(min(eags)), abs(max(eags)))
425
+ for bar, val in zip(bars, eags):
426
+ pad = rng * 0.015
427
+ side = 'left' if val < 0 else 'right'
428
+ xpos = val - pad if val < 0 else val + pad
429
+ ax.text(xpos, bar.get_y() + bar.get_height() / 2,
430
+ f"{val:.2e}", va='center', ha=side,
431
+ color=TEXT_COLOR, fontsize=8.5)
432
+
433
+ ax.set_xlabel("EAG Score (Ξ”Accuracy / Ξ”Joules) Β· Higher = Greener",
434
+ fontsize=11)
435
+ ax.set_title("EAG Leaderboard β€” All EDEN Models vs Baseline",
436
+ fontsize=13, fontweight='bold', pad=12)
437
+ ax.ticklabel_format(axis='x', style='sci', scilimits=(0, 0))
438
+ ax.grid(True, axis='x', linestyle='--', alpha=0.3)
439
+ for sp in ax.spines.values(): sp.set_color(GRID_COLOR)
440
+
441
+ legend_handles = [
442
+ mpatches.Patch(color=GREEN_LT, label="Positive EAG (greener than baseline)"),
443
+ mpatches.Patch(color=RED, label="Negative EAG (accuracy cost more energy)"),
444
+ ]
445
+ ax.legend(handles=legend_handles, facecolor=CARD_BG, edgecolor=GRID_COLOR,
446
+ labelcolor=TEXT_COLOR, fontsize=9)
447
+
448
+ plt.tight_layout()
449
+ plt.savefig(out_path, dpi=150, bbox_inches='tight', facecolor=DARK_BG)
450
+ plt.close()
451
+ print(" βœ“ eag_leaderboard.png")
452
+
453
+
454
+ def generate_co2_comparison(out_path):
455
+ """Grouped bar: COβ‚‚ emissions β€” Baseline vs EDEN per architecture+dataset."""
456
+ setup_dark_style()
457
+
458
+ pairs = {}
459
+ for key, stat in stats_map.items():
460
+ folder = key.split("_")[0]
461
+ arch, ds = parse_name(stat["file"])
462
+ if arch == "unknown" or stat["energy"] == 0: continue
463
+ label = f"{arch}\n({ds})"
464
+ pairs.setdefault(label, {})[folder] = stat
465
+
466
+ # Keep only pairs that have both baseline (test2) and EDEN (test3)
467
+ pairs = {k: v for k, v in pairs.items() if "test2" in v and "test3" in v}
468
+ if not pairs: return
469
+
470
+ labels = sorted(pairs.keys())
471
+ base_co2 = [co2_kg(pairs[l]["test2"]["energy"]) for l in labels]
472
+ eden_co2 = [co2_kg(pairs[l]["test3"]["energy"]) for l in labels]
473
+
474
+ x = np.arange(len(labels))
475
+ width = 0.38
476
+
477
+ fig, ax = plt.subplots(figsize=(max(12, len(labels) * 1.6), 6))
478
+ fig.patch.set_facecolor(DARK_BG)
479
+ ax.set_facecolor(CARD_BG)
480
+
481
+ ax.bar(x - width / 2, base_co2, width, label="Baseline", color=MUTED,
482
+ alpha=0.85, edgecolor=GRID_COLOR)
483
+ ax.bar(x + width / 2, eden_co2, width, label="EDEN Classic", color=GREEN_LT,
484
+ alpha=0.85, edgecolor=GRID_COLOR)
485
+
486
+ for i, (bc, ec) in enumerate(zip(base_co2, eden_co2)):
487
+ if bc > 0:
488
+ saving = (bc - ec) / bc * 100
489
+ color = GREEN_LT if saving > 0 else RED
490
+ ax.text(x[i], max(bc, ec) * 1.04,
491
+ f"{saving:+.1f}%", ha='center', va='bottom',
492
+ color=color, fontsize=9, fontweight='bold')
493
+
494
+ ax.set_xticks(x)
495
+ ax.set_xticklabels(labels, fontsize=8.5)
496
+ ax.set_ylabel("COβ‚‚ Emissions (kg COβ‚‚e)", fontsize=11)
497
+ ax.set_title("COβ‚‚ Emissions β€” Baseline vs EDEN Classic (per architecture)",
498
+ fontsize=13, fontweight='bold', pad=12)
499
+ ax.legend(facecolor=CARD_BG, edgecolor=GRID_COLOR, labelcolor=TEXT_COLOR, fontsize=10)
500
+ ax.grid(True, axis='y', linestyle='--', alpha=0.3)
501
+ for sp in ax.spines.values(): sp.set_color(GRID_COLOR)
502
+
503
+ plt.tight_layout()
504
+ plt.savefig(out_path, dpi=150, bbox_inches='tight', facecolor=DARK_BG)
505
+ plt.close()
506
+ print(" βœ“ co2_comparison.png")
507
+
508
+ # ═══════════════════════════════════════════════════════════════════════════════
509
+ # README BUILDER
510
+ # ═══════════════════════════════════════════════════════════════════════════════
511
+
512
+ def build_model_readme(model, has_tc, has_eag):
513
+ arch = model["arch"]
514
+ dataset = model["dataset"]
515
+ folder = model["folder"]
516
+ acc = model["accuracy"]
517
+ energy = model["energy"]
518
+ t = model["time"]
519
+ phase = folder_to_phase(folder)
520
+ ds_meta = DATASET_META.get(dataset, DATASET_META["unknown"])
521
+ model_co2 = co2_kg(energy)
522
+
523
+ baseline = baselines.get(dataset, {})
524
+ b_acc = baseline.get("accuracy", 0)
525
+ b_energy = baseline.get("energy", 0)
526
+ b_arch = parse_name(baseline.get("file", ""))[0] if baseline else "Baseline"
527
+
528
+ if b_energy and energy and b_energy != energy:
529
+ d_acc = acc - b_acc
530
+ d_j = energy - b_energy
531
+ eag = d_acc / d_j
532
+ eag_str = f"{eag:.4e}"
533
+ savings_str = f"{(b_energy - energy) / b_energy * 100:.2f}%"
534
+ acc_delta = f"{d_acc * 100:+.2f}%"
535
+ else:
536
+ eag_str = "N/A"
537
+ savings_str = "N/A"
538
+ acc_delta = "N/A"
539
+
540
+ # Pull final F1 from CSV
541
+ f1_val = None
542
+ if model["csv"] != "N/A":
543
+ csv_abs = os.path.join(BASE_DIR, model["csv"])
544
+ if os.path.exists(csv_abs):
545
+ try:
546
+ df = pd.read_csv(csv_abs)
547
+ if "f1_score" in df.columns:
548
+ f1_val = float(df["f1_score"].iloc[-1])
549
+ except Exception:
550
+ pass
551
+
552
+ # model-index YAML (enables HF native metrics widget)
553
+ metrics_yaml = f" - type: accuracy\n value: {acc:.4f}\n name: Accuracy"
554
+ if f1_val is not None:
555
+ metrics_yaml += f"\n - type: f1\n value: {f1_val:.4f}\n name: F1 Score"
556
+
557
+ model_index = f"""model-index:
558
+ - name: EDEN-{arch}-{dataset}
559
+ results:
560
+ - task:
561
+ type: image-classification
562
+ name: Image Classification
563
+ dataset:
564
+ name: {dataset}
565
+ type: {ds_meta['hf_name']}
566
+ metrics:
567
+ {metrics_yaml}"""
568
+
569
+ arch_tag = arch.lower().replace(" ", "")
570
+ yaml_co2 = f"{model_co2:.4f}" if model_co2 else "0"
571
+
572
+ frontmatter = f"""---
573
+ language: en
574
+ license: apache-2.0
575
+ tags:
576
+ - image-classification
577
+ - green-ai
578
+ - energy-efficiency
579
+ - computer-vision
580
+ - {arch_tag}
581
+ - eden-framework
582
+ - e2am
583
+ - sustainable-ai
584
+ datasets:
585
+ - {ds_meta['hf_name']}
586
+ metrics:
587
+ - accuracy
588
+ co2_eq_emissions:
589
+ emissions: {yaml_co2}
590
+ unit: kg
591
+ source: Estimated via CodeCarbon (grid factor 0.475 kg CO2e/kWh)
592
+ hardware_used: NVIDIA GeForce GTX 1080 Ti
593
+ dataset_info:
594
+ dataset_size: "{ds_meta['size']}"
595
+ {model_index}
596
+ ---"""
597
+
598
+ green_table = f"""| Metric | {b_arch} Baseline | **{arch} (EDEN)** | Ξ” |
599
+ |---|---|---|---|
600
+ | Accuracy | {b_acc:.4f} | **{acc:.4f}** | `{acc_delta}` |
601
+ | Total Energy (J) | {b_energy:,.0f} | **{energy:,.0f}** | `{savings_str} saved` |
602
+ | COβ‚‚ Emissions (kg) | {co2_kg(b_energy):.4f} | **{model_co2:.4f}** | β€” |
603
+ | **EAG Score** | β€” | **{eag_str}** | Ξ”Acc/Ξ”Joules |"""
604
+
605
+ chart_section = ""
606
+ if has_tc or has_eag:
607
+ chart_section = "\n## πŸ“Š Training Visualizations\n"
608
+ if has_tc:
609
+ chart_section += (
610
+ "\n### Accuracy & Energy over Training\n"
611
+ "> Green = accuracy (left axis) Β· Orange dashed = cumulative energy (right axis)\n\n"
612
+ "![Training Curve](training_curve.png)\n"
613
+ )
614
+ if has_eag:
615
+ chart_section += (
616
+ "\n### EAG Metric Trajectory\n"
617
+ "> EAG = Ξ”Accuracy / Ξ”Joules β€” positive means learning more per Joule than baseline\n\n"
618
+ "![EAG Curve](eag_curve.png)\n"
619
+ )
620
+ chart_section += (
621
+ f"\n### Project-Wide Overview\n"
622
+ f"*All EDEN models: energy vs accuracy*\n\n"
623
+ f"![Collection Overview](https://huggingface.co/{HF_ORG}/EDEN-Core-Scripts/resolve/main/energy_accuracy_overview.png)\n"
624
+ )
625
+
626
+ cite = f"""## Cite This Research
627
+ ```bibtex
628
+ @misc{{eden2025,
629
+ title = {{Project EDEN: Energy-Driven Evolution of Networks}},
630
+ author = {{EDEN Research Team}},
631
+ year = {{2025}},
632
+ note = {{Hugging Face: {HF_ORG}}},
633
+ url = {{https://huggingface.co/{HF_ORG}}}
634
+ }}
635
+ ```"""
636
+
637
+ return f"""{frontmatter}
638
+
639
+ # EDEN-{arch}-{dataset} β€” *{phase}*
640
+
641
+ > **Primary KPI:** EAG (Energy-to-Accuracy Gradient) = `{eag_str}` Ξ”Acc/Ξ”Joules
642
+
643
+ ## Abstract
644
+ This model is part of **Project EDEN (Energy-Driven Evolution of Networks)**, implementing the
645
+ **E2AM (Energy Efficient Advanced Model)** Framework. The goal is to shift AI benchmarking from
646
+ pure accuracy to *Green SOTA* β€” maximising predictive power per Joule consumed.
647
+
648
+ **Applied Technique:** {PHASE_MAP.get(folder, phase)}
649
+
650
+ ## Profiling Environment
651
+ | Component | Specification |
652
+ |---|---|
653
+ | **GPU** | {HARDWARE['gpu']} |
654
+ | **CPU** | {HARDWARE['cpu']} |
655
+ | **RAM** | {HARDWARE['ram']} |
656
+ | **OS** | {HARDWARE['os']} |
657
+ | **Dataset** | {dataset} β€” {ds_meta['size']} |
658
+
659
+ ## 🟒 Green Delta Table
660
+ *Comparing this model against the reference baseline (ResNet-50 equivalent)*
661
+
662
+ {green_table}
663
+
664
+ > A **positive EAG** means this model learns more per Joule than the baseline.
665
+ > A **negative EAG** indicates a trade-off where higher accuracy required more energy investment.
666
+
667
+ ## E2AM Algorithm β€” Applied Phases
668
+
669
+ {PHASE_DETAIL.get(folder, 'Standard training.')}
670
+
671
+ ## Training Statistics
672
+ | Metric | Value |
673
+ |---|---|
674
+ | Final Accuracy | {acc:.4f} ({acc * 100:.2f}%) |
675
+ | Total Energy Consumed | {energy:,.0f} J ({energy / 3_600_000:.4f} kWh) |
676
+ | Training Time | {t:,.0f} s ({t / 3600:.2f} hrs) |
677
+ | Estimated COβ‚‚ | {model_co2:.4f} kg COβ‚‚e |
678
+ | Training Log | `{model['csv']}` |
679
+ {chart_section}
680
+ {cite}
681
+ """
682
+
683
+
684
+ def build_core_scripts_readme():
685
+ py_scripts = sorted(
686
+ os.path.relpath(p, BASE_DIR)
687
+ for p in glob.glob(os.path.join(BASE_DIR, "**/*.py"), recursive=True)
688
+ if any(k in p for k in ["Algo_", "eden_", "mobilevit_model"])
689
+ )
690
+ scripts_md = "\n".join(f"- `{s}`" for s in py_scripts)
691
+
692
+ return f"""---
693
+ language: en
694
+ license: apache-2.0
695
+ tags:
696
+ - green-ai
697
+ - energy-efficiency
698
+ - e2am
699
+ - eden-framework
700
+ - sustainable-ai
701
+ - image-classification
702
+ ---
703
+
704
+ # EDEN-Core-Scripts β€” E2AM Framework Repository
705
+
706
+ > **Project EDEN (Energy-Driven Evolution of Networks)** β€” The complete algorithmic
707
+ > toolkit for Green SOTA image classification research.
708
+
709
+ ## Why EDEN?
710
+ As deep learning models scale exponentially, the carbon footprint of training has reached
711
+ unsustainable levels. Project EDEN introduces the **EAG (Energy-to-Accuracy Gradient)** as
712
+ the primary KPI β€” shifting the paradigm from chasing raw accuracy to optimising *Green SOTA*.
713
+
714
+ ## Profiling Environment
715
+ | Component | Specification |
716
+ |---|---|
717
+ | **GPU** | {HARDWARE['gpu']} |
718
+ | **CPU** | {HARDWARE['cpu']} |
719
+ | **RAM** | {HARDWARE['ram']} |
720
+ | **OS** | {HARDWARE['os']} |
721
+
722
+ ---
723
+
724
+ ## πŸ“Š Collection Overview
725
+
726
+ ### Energy vs Accuracy β€” All Models
727
+ *SOTA Optimized (green) Β· Baseline (grey) Β· EDEN Classic (blue)*
728
+
729
+ ![Energy vs Accuracy](energy_accuracy_overview.png)
730
+
731
+ ### EAG Leaderboard β€” Ranked by Green Efficiency
732
+ ![EAG Leaderboard](eag_leaderboard.png)
733
+
734
+ ### COβ‚‚ Emissions β€” Baseline vs EDEN Classic
735
+ ![CO2 Comparison](co2_comparison.png)
736
+
737
+ ---
738
+
739
+ ## The E2AM Algorithm
740
+
741
+ ### Phase 1 β€” Zero-Overhead Initialization
742
+ Dataset pre-loaded into **pinned System RAM** before training β€” eliminates disk I/O power spikes.
743
+
744
+ ### Phase 2 β€” Two-Stage Energy-Aware Training
745
+ 1. **Frozen Head Training** β€” Only the classification head trains for `E_unfreeze` epochs.
746
+ 2. **Progressive Unfreezing** β€” All layers unlock at `E_unfreeze`; LR decayed (`Γ—0.1`).
747
+ 3. **Gradient Accumulation** β€” Simulates large batch sizes without VRAM spikes.
748
+ 4. **AMP** β€” `torch.cuda.amp.autocast()` halves bandwidth per backward pass.
749
+ 5. **Sparse L1 Penalty** β€” `L_total = CrossEntropy + λ·Σ|W_trainable|`
750
+ 6. **EAG Early-Exit** β€” Terminates if `EAG < Ξ³_EAG` for 3 consecutive epochs.
751
+
752
+ ### Phase 3 β€” Hardware-Aware Deployment *(Post-Training)*
753
+ Saliency-energy pruning Β· INT8 quantization Β· Dynamic depth routing
754
+
755
+ ## EAG β€” The Expert KPI
756
+ ```
757
+ EAG = Ξ”Accuracy / Ξ”Joules
758
+ ```
759
+ A higher EAG = more learning per unit of carbon footprint.
760
+
761
+ ## Scripts in This Repository
762
+ {scripts_md}
763
+
764
+ ## Cite This Research
765
+ ```bibtex
766
+ @misc{{eden2025,
767
+ title = {{Project EDEN: Energy-Driven Evolution of Networks}},
768
+ author = {{EDEN Research Team}},
769
+ year = {{2025}},
770
+ note = {{Hugging Face: {HF_ORG}}},
771
+ url = {{https://huggingface.co/{HF_ORG}}}
772
+ }}
773
+ ```
774
+ """
775
+
776
+ # ═══════════════════════════════════════════════════════════════════════════════
777
+ # MAIN
778
+ # ═══════════════════════════════════════════════════════════════════════════════
779
+
780
+ if __name__ == "__main__":
781
+ print("=" * 65)
782
+ print(" EDEN Chart Generator & HF Pusher")
783
+ print("=" * 65)
784
+
785
+ # ── 1. Collection charts ──────────────────────────────────────────────────
786
+ print("\n[1/3] Generating collection charts...")
787
+ generate_energy_accuracy_overview(os.path.join(CHARTS_DIR, "energy_accuracy_overview.png"))
788
+ generate_eag_leaderboard(os.path.join(CHARTS_DIR, "eag_leaderboard.png"))
789
+ generate_co2_comparison(os.path.join(CHARTS_DIR, "co2_comparison.png"))
790
+
791
+ # ── 2. Per-model charts + READMEs ─────────────────────────────────────────
792
+ print("\n[2/3] Generating per-model charts and READMEs...")
793
+ chart_flags = {} # repo_name -> (has_tc, has_eag)
794
+
795
+ for repo_name, m in repo_model_map.items():
796
+ model_chart_dir = os.path.join(CHARTS_DIR, repo_name)
797
+ os.makedirs(model_chart_dir, exist_ok=True)
798
+
799
+ tc_path = os.path.join(model_chart_dir, "training_curve.png")
800
+ eag_path = os.path.join(model_chart_dir, "eag_curve.png")
801
+
802
+ has_tc = generate_training_curve(m, tc_path)
803
+ has_eag = generate_eag_curve(m, eag_path)
804
+ chart_flags[repo_name] = (has_tc, has_eag)
805
+
806
+ readme_text = build_model_readme(m, has_tc, has_eag)
807
+ readme_path = os.path.join(HF_READMES_DIR, f"{repo_name}_README.md")
808
+ with open(readme_path, "w", encoding="utf-8") as f:
809
+ f.write(readme_text)
810
+ print(f" βœ“ {repo_name:<45} curve={has_tc} eag={has_eag}")
811
+
812
+ core_readme_text = build_core_scripts_readme()
813
+ core_readme_path = os.path.join(HF_READMES_DIR, "EDEN-Core-Scripts_README.md")
814
+ with open(core_readme_path, "w", encoding="utf-8") as f:
815
+ f.write(core_readme_text)
816
+ print(" βœ“ EDEN-Core-Scripts README")
817
+
818
+ # ── 3. Upload to HuggingFace ──────────────────────────────────────────────
819
+ print("\n[3/3] Uploading to HuggingFace...")
820
+
821
+ # Core scripts repo: README + collection charts + .py scripts
822
+ print(" Uploading EDEN-Core-Scripts...")
823
+ try:
824
+ create_repo(repo_id=f"{HF_ORG}/EDEN-Core-Scripts", token=HF_TOKEN,
825
+ repo_type="model", exist_ok=True, private=False)
826
+ upload_file(path_or_fileobj=core_readme_path, path_in_repo="README.md",
827
+ repo_id=f"{HF_ORG}/EDEN-Core-Scripts", token=HF_TOKEN, repo_type="model")
828
+ for chart in ["energy_accuracy_overview.png", "eag_leaderboard.png", "co2_comparison.png"]:
829
+ chart_abs = os.path.join(CHARTS_DIR, chart)
830
+ if os.path.exists(chart_abs):
831
+ upload_file(path_or_fileobj=chart_abs, path_in_repo=chart,
832
+ repo_id=f"{HF_ORG}/EDEN-Core-Scripts", token=HF_TOKEN, repo_type="model")
833
+ for py in glob.glob(os.path.join(BASE_DIR, "**/*.py"), recursive=True):
834
+ rel = os.path.relpath(py, BASE_DIR)
835
+ if any(k in rel for k in ["Algo_", "eden_", "mobilevit_model"]):
836
+ upload_file(path_or_fileobj=py, path_in_repo=rel.replace("\\", "/"),
837
+ repo_id=f"{HF_ORG}/EDEN-Core-Scripts", token=HF_TOKEN, repo_type="model")
838
+ print(" βœ“ EDEN-Core-Scripts")
839
+ except Exception as e:
840
+ print(f" βœ— Core-Scripts: {e}")
841
+
842
+ # Per-model repos
843
+ for repo_name, m in repo_model_map.items():
844
+ has_tc, has_eag = chart_flags[repo_name]
845
+ model_chart_dir = os.path.join(CHARTS_DIR, repo_name)
846
+ readme_path = os.path.join(HF_READMES_DIR, f"{repo_name}_README.md")
847
+ try:
848
+ create_repo(repo_id=f"{HF_ORG}/{repo_name}", token=HF_TOKEN,
849
+ repo_type="model", exist_ok=True, private=False)
850
+ upload_file(path_or_fileobj=readme_path, path_in_repo="README.md",
851
+ repo_id=f"{HF_ORG}/{repo_name}", token=HF_TOKEN, repo_type="model")
852
+ if has_tc:
853
+ upload_file(
854
+ path_or_fileobj=os.path.join(model_chart_dir, "training_curve.png"),
855
+ path_in_repo="training_curve.png",
856
+ repo_id=f"{HF_ORG}/{repo_name}", token=HF_TOKEN, repo_type="model")
857
+ if has_eag:
858
+ upload_file(
859
+ path_or_fileobj=os.path.join(model_chart_dir, "eag_curve.png"),
860
+ path_in_repo="eag_curve.png",
861
+ repo_id=f"{HF_ORG}/{repo_name}", token=HF_TOKEN, repo_type="model")
862
+ # Weights
863
+ pth_abs = os.path.join(BASE_DIR, m["pth"])
864
+ if os.path.exists(pth_abs):
865
+ upload_file(path_or_fileobj=pth_abs,
866
+ path_in_repo=os.path.basename(m["pth"]),
867
+ repo_id=f"{HF_ORG}/{repo_name}", token=HF_TOKEN, repo_type="model")
868
+ # CSV log
869
+ if m["csv"] != "N/A":
870
+ csv_abs = os.path.join(BASE_DIR, m["csv"])
871
+ if os.path.exists(csv_abs):
872
+ upload_file(path_or_fileobj=csv_abs,
873
+ path_in_repo=os.path.basename(m["csv"]),
874
+ repo_id=f"{HF_ORG}/{repo_name}", token=HF_TOKEN, repo_type="model")
875
+ print(f" βœ“ {repo_name}")
876
+ except Exception as e:
877
+ print(f" βœ— {repo_name}: {e}")
878
+
879
+ print("\n" + "=" * 65)
880
+ print(f" Done! https://huggingface.co/{HF_ORG}")
881
+ print("=" * 65)