Agnuxo commited on
Commit
4ad5c37
·
verified ·
1 Parent(s): 3fdbb05

Upload seed/evolution/selector.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. seed/evolution/selector.py +219 -0
seed/evolution/selector.py ADDED
@@ -0,0 +1,219 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Evolution Engine — Natural Selection for AI Models
3
+ =====================================================
4
+ Implements biological evolution principles:
5
+ - Variation: Train with different hyperparameters
6
+ - Selection: Keep the best performing model
7
+ - Inheritance: New training builds on previous best
8
+ - Growth: Upgrade to larger architecture when ready
9
+
10
+ The model evolves like a living organism, keeping what works
11
+ and discarding what doesn't. Over time, it grows from a tiny
12
+ seed into a capable research assistant.
13
+ """
14
+ import json
15
+ import logging
16
+ import os
17
+ import urllib.request
18
+ from datetime import datetime, timezone
19
+ from pathlib import Path
20
+ from typing import Optional
21
+
22
+ logger = logging.getLogger("seed.evolution")
23
+
24
+
25
+ class EvolutionEngine:
26
+ """Natural selection for model versions."""
27
+
28
+ def __init__(self, hf_token: str = None, state_dir: str = "seed_state"):
29
+ self.hf_token = hf_token or os.environ.get("HF_TOKEN", "")
30
+ self.state_dir = Path(state_dir)
31
+ self.state_dir.mkdir(parents=True, exist_ok=True)
32
+ self.evolution_log = self._load_log()
33
+
34
+ def _load_log(self) -> dict:
35
+ log_file = self.state_dir / "evolution_log.json"
36
+ if log_file.exists():
37
+ try:
38
+ return json.loads(log_file.read_text())
39
+ except Exception:
40
+ pass
41
+ return {
42
+ "generation": 0,
43
+ "best_model": None,
44
+ "best_score": 0.0,
45
+ "population": [],
46
+ "history": [],
47
+ }
48
+
49
+ def _save_log(self):
50
+ log_file = self.state_dir / "evolution_log.json"
51
+ log_file.write_text(json.dumps(self.evolution_log, indent=2))
52
+
53
+ def evaluate_model(self, model_name: str, test_data: list[dict] = None) -> dict:
54
+ """
55
+ Evaluate a model's fitness using multiple criteria.
56
+ Uses inference API if available, otherwise heuristics from training report.
57
+ """
58
+ scores = {
59
+ "model": model_name,
60
+ "timestamp": datetime.now(timezone.utc).isoformat(),
61
+ "coherence": 0.0,
62
+ "knowledge": 0.0,
63
+ "relevance": 0.0,
64
+ "overall": 0.0,
65
+ }
66
+
67
+ # Try HuggingFace Inference API evaluation
68
+ if self.hf_token and test_data:
69
+ try:
70
+ scores = self._evaluate_via_inference(model_name, test_data)
71
+ except Exception as e:
72
+ logger.warning(f"Inference eval failed: {e}")
73
+
74
+ # Fallback: evaluate from training metrics
75
+ training_report = self.state_dir / "training_report.json"
76
+ if training_report.exists():
77
+ try:
78
+ report = json.loads(training_report.read_text())
79
+ loss = report.get("final_loss", 10.0)
80
+ # Lower loss = better (invert and normalize)
81
+ loss_score = max(0, min(1, 1.0 - (loss / 5.0)))
82
+
83
+ data_score = min(1.0, report.get("training_entries", 0) / 5000)
84
+ param_score = min(1.0, report.get("total_params", 0) / 7_000_000_000)
85
+
86
+ scores["coherence"] = loss_score
87
+ scores["knowledge"] = data_score
88
+ scores["relevance"] = (loss_score + data_score) / 2
89
+ scores["overall"] = (loss_score * 0.4 + data_score * 0.3 + param_score * 0.3)
90
+
91
+ except Exception as e:
92
+ logger.warning(f"Report eval failed: {e}")
93
+
94
+ return scores
95
+
96
+ def _evaluate_via_inference(self, model_name: str, test_data: list[dict]) -> dict:
97
+ """Evaluate model using HF Inference API."""
98
+ url = f"https://api-inference.huggingface.co/models/{model_name}"
99
+ headers = {
100
+ "Authorization": f"Bearer {self.hf_token}",
101
+ "Content-Type": "application/json",
102
+ }
103
+
104
+ correct = 0
105
+ total = 0
106
+ coherent = 0
107
+
108
+ for test in test_data[:20]: # Test max 20 samples
109
+ prompt = test.get("instruction", "")
110
+ expected = test.get("output", "")
111
+
112
+ payload = json.dumps({
113
+ "inputs": f"### Instruction:\n{prompt}\n\n### Response:\n",
114
+ "parameters": {"max_new_tokens": 200, "temperature": 0.7}
115
+ }).encode()
116
+
117
+ try:
118
+ req = urllib.request.Request(url, data=payload, headers=headers)
119
+ with urllib.request.urlopen(req, timeout=30) as resp:
120
+ result = json.loads(resp.read().decode())
121
+
122
+ generated = result[0].get("generated_text", "")
123
+ total += 1
124
+
125
+ # Simple coherence check: response is not empty and doesn't repeat
126
+ if len(generated) > 20 and generated[:50] != generated[50:100]:
127
+ coherent += 1
128
+
129
+ # Simple relevance: check keyword overlap
130
+ expected_words = set(expected.lower().split())
131
+ gen_words = set(generated.lower().split())
132
+ overlap = len(expected_words & gen_words) / max(len(expected_words), 1)
133
+ if overlap > 0.2:
134
+ correct += 1
135
+
136
+ except Exception:
137
+ continue
138
+
139
+ if total == 0:
140
+ return {"model": model_name, "overall": 0.0}
141
+
142
+ return {
143
+ "model": model_name,
144
+ "timestamp": datetime.now(timezone.utc).isoformat(),
145
+ "coherence": coherent / total,
146
+ "knowledge": correct / total,
147
+ "relevance": (coherent + correct) / (2 * total),
148
+ "overall": (coherent / total * 0.5 + correct / total * 0.5),
149
+ "tested": total,
150
+ }
151
+
152
+ def select_best(self, candidates: list[dict]) -> dict:
153
+ """Select the best model from candidates (natural selection)."""
154
+ if not candidates:
155
+ return self.evolution_log.get("best_model", {})
156
+
157
+ best = max(candidates, key=lambda x: x.get("overall", 0))
158
+
159
+ prev_best = self.evolution_log.get("best_score", 0)
160
+ if best["overall"] > prev_best:
161
+ logger.info(f"🏆 New best model: {best['model']} (score: {best['overall']:.3f} > {prev_best:.3f})")
162
+ self.evolution_log["best_model"] = best
163
+ self.evolution_log["best_score"] = best["overall"]
164
+ else:
165
+ logger.info(f"Current champion still best (score: {prev_best:.3f})")
166
+
167
+ self.evolution_log["generation"] += 1
168
+ self.evolution_log["population"] = candidates
169
+ self.evolution_log["history"].append({
170
+ "generation": self.evolution_log["generation"],
171
+ "best": best["model"],
172
+ "score": best["overall"],
173
+ "timestamp": datetime.now(timezone.utc).isoformat(),
174
+ })
175
+ self.evolution_log["history"] = self.evolution_log["history"][-100:]
176
+ self._save_log()
177
+
178
+ return best
179
+
180
+ def should_grow(self) -> Optional[str]:
181
+ """
182
+ Determine if the model should grow to a larger architecture.
183
+ Growth triggers:
184
+ - Score plateau (>3 cycles without improvement > 5%)
185
+ - Sufficient training data for next stage
186
+ - Current model consistently scoring > 0.7
187
+ """
188
+ history = self.evolution_log.get("history", [])
189
+ if len(history) < 3:
190
+ return None
191
+
192
+ recent_scores = [h["score"] for h in history[-5:]]
193
+
194
+ # Check for plateau
195
+ if len(recent_scores) >= 3:
196
+ variance = max(recent_scores) - min(recent_scores)
197
+ avg_score = sum(recent_scores) / len(recent_scores)
198
+
199
+ if variance < 0.05 and avg_score > 0.6:
200
+ current = self.evolution_log.get("best_model", {}).get("model", "")
201
+ logger.info(f"📈 Growth triggered! Plateau detected at score {avg_score:.3f}")
202
+ return "PLATEAU"
203
+
204
+ # Check if consistently good
205
+ if all(s > 0.7 for s in recent_scores[-3:]):
206
+ logger.info("📈 Growth triggered! Consistently high scores")
207
+ return "MASTERY"
208
+
209
+ return None
210
+
211
+ def get_status(self) -> dict:
212
+ """Get current evolution status."""
213
+ return {
214
+ "generation": self.evolution_log["generation"],
215
+ "best_model": self.evolution_log.get("best_model", {}).get("model", "none"),
216
+ "best_score": self.evolution_log.get("best_score", 0),
217
+ "should_grow": self.should_grow(),
218
+ "total_candidates_evaluated": len(self.evolution_log.get("history", [])),
219
+ }