steffenc CryptAL commited on
Commit
c6826fb
1 Parent(s): 2201bcf

API refactor and benchmark enable

Browse files

* Add flask dependency

* Move common operations to utils for reuse in api

* Remove unused logging

* Remove refactored code

* Add prototype API

* Update utils.py

Co-authored-by: alan-aboudib-mc <alan.aboudib@macrocosmos.ai>

* Update utils.py

Co-authored-by: alan-aboudib-mc <alan.aboudib@macrocosmos.ai>

* Update utils.py

Co-authored-by: alan-aboudib-mc <alan.aboudib@macrocosmos.ai>

* Updated wandb benchmark project

---------

Co-authored-by: alan-aboudib-mc <alan.aboudib@macrocosmos.ai>

Files changed (4) hide show
  1. api.py +112 -0
  2. app.py +20 -333
  3. requirements.txt +2 -0
  4. utils.py +449 -0
api.py ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import utils
2
+ import time
3
+ import datetime
4
+
5
+ import pandas as pd
6
+ import bittensor as bt
7
+ from typing import Dict, List, Any, Optional, Tuple
8
+ from flask import Flask, request, jsonify
9
+
10
+
11
+ app = Flask(__name__)
12
+
13
+ # Global variables (saves time on loading data)
14
+
15
+ state_vars = utils.test_load_state_vars()
16
+ metagraph = state_vars["metagraph"]
17
+ model_data = state_vars["model_data"]
18
+ vali_runs = state_vars["vali_runs"]
19
+ scores = state_vars["scores"]
20
+ validator_df = state_vars["validator_df"]
21
+ benchmarks = state_vars.get("benchmarks", None)
22
+ benchmark_timestamp = state_vars.get("benchmark_timestamp", None)
23
+
24
+ @app.route('/', methods=['GET'])
25
+ def home():
26
+ return "Welcome to the Bittensor Pretraining Leaderboard API!"
27
+
28
+ @app.route('/reload', methods=['GET'])
29
+ def reload():
30
+ """
31
+ Reload the state variables
32
+ """
33
+ global metagraph, model_data, vali_runs, scores, validator_df, benchmarks, benchmark_timestamp
34
+ state_vars = utils.load_state_vars()
35
+ metagraph = state_vars["metagraph"]
36
+ model_data = state_vars["model_data"]
37
+ vali_runs = state_vars["vali_runs"]
38
+ scores = state_vars["scores"]
39
+ validator_df = state_vars["validator_df"]
40
+ benchmarks = state_vars.get("benchmarks", None)
41
+ benchmark_timestamp = state_vars.get("benchmark_timestamp", None)
42
+
43
+ return jsonify({"message": "State variables reloaded"})
44
+
45
+ @app.route('/benchmark', methods=['GET'])
46
+ def benchmark():
47
+ """
48
+ Get the benchmarks and the timestamp
49
+
50
+ Returns:
51
+ - benchmarks: List of dicts (from pandas DataFrame)
52
+ - benchmark_timestamp: String
53
+ """
54
+
55
+ return jsonify(
56
+ {
57
+ "benchmarks": benchmarks.to_dict(orient='records'),
58
+ "benchmark_timestamp": benchmark_timestamp.strftime('%Y-%m-%d %H:%M:%S')
59
+ }
60
+ )
61
+
62
+ @app.route('/metagraph', methods=['GET'])
63
+ def metagraph():
64
+ """
65
+ Get the metagraph data
66
+ Returns:
67
+ - metagraph_data: List of dicts (from pandas DataFrame)
68
+ """
69
+ return jsonify(
70
+ utils.make_metagraph_dataframe(metagraph).to_dict(orient='records')
71
+ )
72
+
73
+ @app.route('/leaderboard', methods=['GET'])
74
+ def leaderboard():
75
+ """
76
+ Get the leaderboard data
77
+ Returns:
78
+ - leaderboard_data: List of dicts (from pandas DataFrame)
79
+ """
80
+ show_stale = request.args.get('show_stale')
81
+ return jsonify(
82
+ utils.leaderboard_data(model_data, scores, show_stale=show_stale)
83
+ )
84
+
85
+
86
+ @app.route('/loss', methods=['GET'])
87
+ def loss():
88
+ """
89
+ Get the losses over time
90
+ Returns:
91
+ - losses_over_time: List of dicts (from pandas DataFrame)
92
+ """
93
+ return jsonify(
94
+ utils.get_losses_over_time(vali_runs).to_dict(orient='records')
95
+ )
96
+
97
+
98
+ @app.route('/validator', methods=['GET'])
99
+ def validator():
100
+ """
101
+ Get the validator data
102
+ Returns:
103
+ - validator_data: List of dicts (from pandas DataFrame)
104
+ """
105
+ return jsonify(
106
+ utils.make_validator_dataframe(validator_df, model_data).to_dict(orient='records')
107
+ )
108
+
109
+
110
+ if __name__ == '__main__':
111
+
112
+ app.run(port=5000, debug=True)
app.py CHANGED
@@ -1,29 +1,14 @@
1
  # Code adapted from: https://huggingface.co/spaces/RaoFoundation/pretraining-leaderboard/blob/main/app.py
2
 
3
- import argparse
4
- import functools
5
- import traceback
6
- import gradio as gr
7
- import bittensor as bt
8
- from typing import Dict, List, Any, Optional, Tuple
9
- from bittensor.extrinsics.serving import get_metadata
10
- from dataclasses import dataclass
11
- import wandb
12
- import math
13
  import os
14
  import datetime
15
- import time
16
- import json
17
- import pandas as pd
18
- import numpy as np
19
  from dotenv import load_dotenv
20
  from huggingface_hub import HfApi
21
  from apscheduler.schedulers.background import BackgroundScheduler
22
- import pandas as pd
23
-
24
- load_dotenv()
25
-
26
 
 
27
 
28
  FONT = (
29
  """<link href="https://fonts.cdnfonts.com/css/jmh-typewriter" rel="stylesheet">"""
@@ -33,239 +18,15 @@ HEADER = """<h2 align="center" class="typewriter"><a href="https://github.com/ma
33
 
34
  EVALUATION_DETAILS = """<ul><li><b>Name:</b> the 🤗 Hugging Face model name (click to go to the model card)</li><li><b>Rewards / Day:</b> the expected rewards per day based on current ranking.</li><li><b>Last Average Loss:</b> the last loss value on the evaluation data for the model as calculated by a validator (lower is better)</li><li><b>UID:</b> the Bittensor UID of the miner</li><li><b>Block:</b> the Bittensor block that the model was submitted in</li></ul><br/>More stats on <a href="https://taostats.io/subnets/netuid-9/" target="_blank">taostats</a>."""
35
  EVALUATION_HEADER = """<h3 align="center">Shows the latest internal evaluation statistics as calculated by the Opentensor validator</h3>"""
36
- VALIDATOR_WANDB_PROJECT = "opentensor-dev/pretraining-subnet"
37
- #BENCHMARK_WANDB_PROJECT = "raofoundation/pretraining-leaderboard-data"
38
- HF_TOKEN = os.environ.get("HF_TOKEN", None)
39
- API = HfApi(token=HF_TOKEN)
40
- WANDB_TOKEN = os.environ.get("WANDB_API_KEY", None)
41
- SUBTENSOR_ENDPOINT=os.environ.get("SUBTENSOR_ENDPOINT", None)
42
- REPO_ID = "macrocosm-os/sn9"
43
- MAX_AVG_LOSS_POINTS = 1
44
- RETRIES = 5
45
- DELAY_SECS = 3
46
- NETUID = 9
47
- SECONDS_PER_BLOCK = 12
48
-
49
-
50
- @dataclass
51
- class ModelData:
52
- uid: int
53
- hotkey: str
54
- namespace: str
55
- name: str
56
- commit: str
57
- hash: str
58
- block: int
59
- incentive: float
60
- emission: float
61
-
62
- @classmethod
63
- def from_compressed_str(
64
- cls,
65
- uid: int,
66
- hotkey: str,
67
- cs: str,
68
- block: int,
69
- incentive: float,
70
- emission: float,
71
- ):
72
- """Returns an instance of this class from a compressed string representation"""
73
- tokens = cs.split(":")
74
- return ModelData(
75
- uid=uid,
76
- hotkey=hotkey,
77
- namespace=tokens[0],
78
- name=tokens[1],
79
- commit=tokens[2] if tokens[2] != "None" else None,
80
- hash=tokens[3] if tokens[3] != "None" else None,
81
- block=block,
82
- incentive=incentive,
83
- emission=emission,
84
- )
85
-
86
-
87
- def run_with_retries(func, *args, **kwargs):
88
- for i in range(0, RETRIES):
89
- try:
90
- return func(*args, **kwargs)
91
- except (Exception, RuntimeError):
92
- if i == RETRIES - 1:
93
- raise
94
- time.sleep(DELAY_SECS)
95
- raise RuntimeError("Should never happen")
96
-
97
-
98
- def get_subtensor_and_metagraph() -> Tuple[bt.subtensor, bt.metagraph]:
99
- def _internal() -> Tuple[bt.subtensor, bt.metagraph]:
100
- if SUBTENSOR_ENDPOINT:
101
- parser = argparse.ArgumentParser()
102
- bt.subtensor.add_args(parser)
103
- subtensor = bt.subtensor(config=bt.config(parser=parser, args=["--subtensor.chain_endpoint", SUBTENSOR_ENDPOINT]))
104
- else:
105
- subtensor = bt.subtensor("finney")
106
- metagraph = subtensor.metagraph(NETUID, lite=False)
107
- return subtensor, metagraph
108
-
109
- return run_with_retries(_internal)
110
-
111
-
112
- def get_validator_weights(
113
- metagraph: bt.metagraph,
114
- ) -> Dict[int, Tuple[float, int, Dict[int, float]]]:
115
- """Returns a dictionary of validator UIDs to (vtrust, stake, {uid: weight})."""
116
- ret = {}
117
- for uid in metagraph.uids.tolist():
118
- vtrust = metagraph.validator_trust[uid].item()
119
- if vtrust > 0:
120
- ret[uid] = (vtrust, metagraph.S[uid].item(), {})
121
- for ouid in metagraph.uids.tolist():
122
- if ouid == uid:
123
- continue
124
- weight = round(metagraph.weights[uid][ouid].item(), 4)
125
- if weight > 0:
126
- ret[uid][-1][ouid] = weight
127
- return ret
128
-
129
-
130
- def get_subnet_data(
131
- subtensor: bt.subtensor, metagraph: bt.metagraph
132
- ) -> List[ModelData]:
133
- result = []
134
- for uid in metagraph.uids.tolist():
135
- hotkey = metagraph.hotkeys[uid]
136
- metadata = None
137
- try:
138
- metadata = run_with_retries(
139
- functools.partial(get_metadata, subtensor, metagraph.netuid, hotkey)
140
- )
141
- except:
142
- print(f"Failed to get metadata for UID {uid}: {traceback.format_exc()}")
143
-
144
- if not metadata:
145
- continue
146
-
147
- commitment = metadata["info"]["fields"][0]
148
- hex_data = commitment[list(commitment.keys())[0]][2:]
149
- chain_str = bytes.fromhex(hex_data).decode()
150
- block = metadata["block"]
151
-
152
- incentive = np.nan_to_num(metagraph.incentive[uid]).item()
153
- emission = (
154
- np.nan_to_num(metagraph.emission[uid]).item() * 20
155
- ) # convert to daily TAO
156
 
157
- model_data = None
158
- try:
159
- model_data = ModelData.from_compressed_str(
160
- uid, hotkey, chain_str, block, incentive, emission
161
- )
162
- except:
163
- continue
164
-
165
- result.append(model_data)
166
- return result
167
-
168
-
169
- def is_floatable(x) -> bool:
170
- return (
171
- isinstance(x, float) and not math.isnan(x) and not math.isinf(x)
172
- ) or isinstance(x, int)
173
-
174
-
175
- def get_wandb_runs(project: str, filters: Dict[str, Any]) -> List:
176
- """Get the latest runs from Wandb, retrying infinitely until we get them."""
177
- while True:
178
- api = wandb.Api(api_key=WANDB_TOKEN)
179
- runs = list(
180
- api.runs(
181
- project,
182
- filters=filters,
183
- )
184
- )
185
- if len(runs) > 0:
186
- return runs
187
- # WandDB API is quite unreliable. Wait another minute and try again.
188
- print("Failed to get runs from Wandb. Trying again in 60 seconds.")
189
- time.sleep(60)
190
-
191
-
192
- def get_scores(
193
- uids: List[int],
194
- wandb_runs: List,
195
- ) -> Dict[int, Dict[str, Optional[float]]]:
196
- result = {}
197
- previous_timestamp = None
198
- # Iterate through the runs until we've processed all the uids.
199
- for i, run in enumerate(wandb_runs):
200
- if not "original_format_json" in run.summary:
201
- continue
202
- data = json.loads(run.summary["original_format_json"])
203
- all_uid_data = data["uid_data"]
204
- timestamp = data["timestamp"]
205
- # Make sure runs are indeed in descending time order.
206
- #assert (
207
- #previous_timestamp is None or timestamp < previous_timestamp
208
- #), f"Timestamps are not in descending order: {timestamp} >= {previous_timestamp}"
209
- previous_timestamp = timestamp
210
-
211
- for uid in uids:
212
- if uid in result:
213
- continue
214
- if str(uid) in all_uid_data:
215
- uid_data = all_uid_data[str(uid)]
216
- # Only the most recent run is fresh.
217
- is_fresh = i == 0
218
- result[uid] = {
219
- "avg_loss": uid_data.get("average_loss", None),
220
- "win_rate": uid_data.get("win_rate", None),
221
- "win_total": uid_data.get("win_total", None),
222
- "weight": uid_data.get("weight", None),
223
- "fresh": is_fresh,
224
- }
225
- if len(result) == len(uids):
226
- break
227
- return result
228
-
229
-
230
- def get_losses_over_time(wandb_runs: List) -> pd.DataFrame:
231
- """Returns a dataframe of the best average model loss over time."""
232
- timestamps = []
233
- best_losses = []
234
 
235
- for run in wandb_runs:
236
- if "original_format_json" not in run.summary:
237
- continue
238
- data = json.loads(run.summary["original_format_json"])
239
- all_uid_data = data["uid_data"]
240
- timestamp = datetime.datetime.fromtimestamp(data["timestamp"])
241
- best_loss = math.inf
242
- for _, uid_data in all_uid_data.items():
243
- loss = uid_data.get("average_loss", math.inf)
244
- # Filter out the numbers from the exploit and when validators lost the best model.
245
- if loss < best_loss and (loss > 2.5 or timestamp > datetime.datetime(2024,2,12)) and (loss < 5 or timestamp > datetime.datetime(2024,3,27)):
246
- best_loss = uid_data["average_loss"]
247
- if best_loss != math.inf:
248
- timestamps.append(timestamp)
249
- best_losses.append(best_loss)
250
-
251
- return pd.DataFrame({"timestamp": timestamps, "best_loss": best_losses})
252
-
253
-
254
- def format_score(uid: int, scores, key) -> Optional[float]:
255
- if uid in scores:
256
- if key in scores[uid]:
257
- point = scores[uid][key]
258
- if is_floatable(point):
259
- return round(scores[uid][key], 4)
260
- return None
261
 
 
262
 
263
- def next_epoch(subtensor: bt.subtensor, block: int) -> int:
264
- return (
265
- block
266
- + subtensor.get_subnet_hyperparameters(NETUID).tempo
267
- - subtensor.blocks_since_epoch(NETUID, block)
268
- )
269
 
270
 
271
  def get_next_update_div(current_block: int, next_update_block: int) -> str:
@@ -282,69 +43,21 @@ def get_last_updated_div() -> str:
282
  return f"""<div>Last Updated: {datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")} (UTC)</div>"""
283
 
284
 
285
- def leaderboard_data(
286
- leaderboard: List[ModelData],
287
- scores: Dict[int, Dict[str, Optional[float]]],
288
- show_stale: bool,
289
- ) -> List[List[Any]]:
290
- """Returns the leaderboard data, based on models data and UID scores."""
291
- return [
292
- [
293
- f"[{c.namespace}/{c.name} ({c.commit[0:8]})](https://huggingface.co/{c.namespace}/{c.name}/commit/{c.commit})",
294
- format_score(c.uid, scores, "win_rate"),
295
- format_score(c.uid, scores, "avg_loss"),
296
- format_score(c.uid, scores, "weight"),
297
- c.uid,
298
- c.block,
299
- ]
300
- for c in leaderboard
301
- if (c.uid in scores and scores[c.uid]["fresh"]) or show_stale
302
- ]
303
-
304
- def get_benchmarks() -> Tuple[pd.DataFrame, datetime.datetime]:
305
- """Returns the latest benchmarks and the time they were run."""
306
- runs = get_wandb_runs(project=BENCHMARK_WANDB_PROJECT, filters=None)
307
- for run in runs:
308
- artifacts = list(run.logged_artifacts())
309
- if artifacts:
310
- table = artifacts[-1].get("benchmarks")
311
- if table:
312
- return table.get_dataframe(), datetime.datetime.strptime(run.metadata["startedAt"], "%Y-%m-%dT%H:%M:%S.%f")
313
- bt.logging.error("Failed to get benchmarks from Wandb.")
314
- return None, None
315
-
316
-
317
  def restart_space():
318
- API.restart_space(repo_id=REPO_ID, token=HF_TOKEN)
319
 
320
 
321
  def main():
322
  # To avoid leaderboard failures, infinitely try until we get all data
323
  # needed to populate the dashboard
324
- while True:
325
- try:
326
- subtensor, metagraph = get_subtensor_and_metagraph()
327
-
328
- model_data: List[ModelData] = get_subnet_data(subtensor, metagraph)
329
- model_data.sort(key=lambda x: x.incentive, reverse=True)
330
- vali_runs = get_wandb_runs(project=VALIDATOR_WANDB_PROJECT, filters={"config.type": "validator", "config.uid": 238})
331
-
332
- scores = get_scores([x.uid for x in model_data], vali_runs)
333
-
334
- # TODO: Re-enable once ""SubtensorModule.BlocksSinceEpoch" not found" issue is resolved.
335
- # current_block = metagraph.block.item()
336
- # next_epoch_block = next_epoch(subtensor, current_block)
337
 
338
- validator_df = get_validator_weights(metagraph)
339
- weight_keys = set()
340
- for uid, stats in validator_df.items():
341
- weight_keys.update(stats[-1].keys())
342
-
343
- #benchmarks, benchmark_timestamp = get_benchmarks()
344
- break
345
- except Exception as e:
346
- print(f"Failed to get data: {e}")
347
- time.sleep(30)
348
 
349
  demo = gr.Blocks(css=".typewriter {font-family: 'JMH Typewriter', sans-serif;}")
350
  with demo:
@@ -363,20 +76,17 @@ def main():
363
  },
364
  num_top_classes=10,
365
  )
366
-
367
- '''
368
  if benchmarks is not None:
369
  with gr.Accordion("Top Model Benchmarks"):
370
  gr.components.Dataframe(benchmarks)
371
  gr.HTML("""<div>PPL computed using a stride of 512. See <a href='https://github.com/macrocosm-os/pretraining/blob/dev/scripts/run_benchmarks.py'>here</a> for the full code.</div>""")
372
  gr.HTML(f"""<div>Last Updated: {benchmark_timestamp.strftime("%Y-%m-%d %H:%M:%S")} (UTC)</div>""")
373
- '''
374
 
375
  with gr.Accordion("Evaluation Stats"):
376
  gr.HTML(EVALUATION_HEADER)
377
  show_stale = gr.Checkbox(label="Show Stale", interactive=True)
378
  leaderboard_table = gr.components.Dataframe(
379
- value=leaderboard_data(model_data, scores, show_stale.value),
380
  headers=["Name", "Win Rate", "Average Loss", "Weight", "UID", "Block"],
381
  datatype=["markdown", "number", "number", "number", "number", "number"],
382
  elem_id="leaderboard-table",
@@ -385,13 +95,13 @@ def main():
385
  )
386
  gr.HTML(EVALUATION_DETAILS)
387
  show_stale.change(
388
- lambda stale: leaderboard_data(model_data, scores, stale),
389
  inputs=[show_stale],
390
  outputs=leaderboard_table,
391
  )
392
 
393
  gr.LinePlot(
394
- get_losses_over_time(vali_runs),
395
  x="timestamp",
396
  x_title="Date",
397
  y="best_loss",
@@ -405,30 +115,7 @@ def main():
405
 
406
  with gr.Accordion("Validator Stats"):
407
  gr.components.Dataframe(
408
- value=[
409
- [uid, int(validator_df[uid][1]), round(validator_df[uid][0], 4)]
410
- + [
411
- validator_df[uid][-1].get(c.uid)
412
- for c in model_data
413
- if c.incentive
414
- ]
415
- for uid, _ in sorted(
416
- zip(
417
- validator_df.keys(),
418
- [validator_df[x][1] for x in validator_df.keys()],
419
- ),
420
- key=lambda x: x[1],
421
- reverse=True,
422
- )
423
- ],
424
- headers=["UID", "Stake (τ)", "V-Trust"]
425
- + [
426
- f"{c.namespace}/{c.name} ({c.commit[0:8]})"
427
- for c in model_data
428
- if c.incentive
429
- ],
430
- datatype=["number", "number", "number"]
431
- + ["number" for c in model_data if c.incentive],
432
  interactive=False,
433
  visible=True,
434
  )
 
1
  # Code adapted from: https://huggingface.co/spaces/RaoFoundation/pretraining-leaderboard/blob/main/app.py
2
 
 
 
 
 
 
 
 
 
 
 
3
  import os
4
  import datetime
5
+ import gradio as gr
6
+
 
 
7
  from dotenv import load_dotenv
8
  from huggingface_hub import HfApi
9
  from apscheduler.schedulers.background import BackgroundScheduler
 
 
 
 
10
 
11
+ import utils
12
 
13
  FONT = (
14
  """<link href="https://fonts.cdnfonts.com/css/jmh-typewriter" rel="stylesheet">"""
 
18
 
19
  EVALUATION_DETAILS = """<ul><li><b>Name:</b> the 🤗 Hugging Face model name (click to go to the model card)</li><li><b>Rewards / Day:</b> the expected rewards per day based on current ranking.</li><li><b>Last Average Loss:</b> the last loss value on the evaluation data for the model as calculated by a validator (lower is better)</li><li><b>UID:</b> the Bittensor UID of the miner</li><li><b>Block:</b> the Bittensor block that the model was submitted in</li></ul><br/>More stats on <a href="https://taostats.io/subnets/netuid-9/" target="_blank">taostats</a>."""
20
  EVALUATION_HEADER = """<h3 align="center">Shows the latest internal evaluation statistics as calculated by the Opentensor validator</h3>"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
23
+ HF_REPO_ID = "macrocosm-os/pretraining-leaderboard"
24
+ SECONDS_PER_BLOCK = 12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
 
26
+ load_dotenv()
27
 
28
+ HF_TOKEN = os.environ.get("HF_TOKEN", None)
29
+ API = HfApi(token=HF_TOKEN)
 
 
 
 
30
 
31
 
32
  def get_next_update_div(current_block: int, next_update_block: int) -> str:
 
43
  return f"""<div>Last Updated: {datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")} (UTC)</div>"""
44
 
45
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  def restart_space():
47
+ API.restart_space(repo_id=HF_REPO_ID, token=HF_TOKEN)
48
 
49
 
50
  def main():
51
  # To avoid leaderboard failures, infinitely try until we get all data
52
  # needed to populate the dashboard
 
 
 
 
 
 
 
 
 
 
 
 
 
53
 
54
+ state_vars = utils.load_state_vars()
55
+ model_data = state_vars["model_data"]
56
+ vali_runs = state_vars["vali_runs"]
57
+ scores = state_vars["scores"]
58
+ validator_df = state_vars["validator_df"]
59
+ benchmarks = state_vars.get("benchmarks", None)
60
+ benchmark_timestamp = state_vars.get("benchmark_timestamp", None)
 
 
 
61
 
62
  demo = gr.Blocks(css=".typewriter {font-family: 'JMH Typewriter', sans-serif;}")
63
  with demo:
 
76
  },
77
  num_top_classes=10,
78
  )
 
 
79
  if benchmarks is not None:
80
  with gr.Accordion("Top Model Benchmarks"):
81
  gr.components.Dataframe(benchmarks)
82
  gr.HTML("""<div>PPL computed using a stride of 512. See <a href='https://github.com/macrocosm-os/pretraining/blob/dev/scripts/run_benchmarks.py'>here</a> for the full code.</div>""")
83
  gr.HTML(f"""<div>Last Updated: {benchmark_timestamp.strftime("%Y-%m-%d %H:%M:%S")} (UTC)</div>""")
 
84
 
85
  with gr.Accordion("Evaluation Stats"):
86
  gr.HTML(EVALUATION_HEADER)
87
  show_stale = gr.Checkbox(label="Show Stale", interactive=True)
88
  leaderboard_table = gr.components.Dataframe(
89
+ value=utils.leaderboard_data(model_data, scores, show_stale.value),
90
  headers=["Name", "Win Rate", "Average Loss", "Weight", "UID", "Block"],
91
  datatype=["markdown", "number", "number", "number", "number", "number"],
92
  elem_id="leaderboard-table",
 
95
  )
96
  gr.HTML(EVALUATION_DETAILS)
97
  show_stale.change(
98
+ lambda stale: utils.leaderboard_data(model_data, scores, stale),
99
  inputs=[show_stale],
100
  outputs=leaderboard_table,
101
  )
102
 
103
  gr.LinePlot(
104
+ utils.get_losses_over_time(vali_runs),
105
  x="timestamp",
106
  x_title="Date",
107
  y="best_loss",
 
115
 
116
  with gr.Accordion("Validator Stats"):
117
  gr.components.Dataframe(
118
+ utils.make_validator_dataframe(validator_df, model_data),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  interactive=False,
120
  visible=True,
121
  )
requirements.txt CHANGED
@@ -4,5 +4,7 @@ wandb
4
  python-dotenv
5
  APScheduler
6
  huggingface-hub
 
7
  pandas
 
8
 
 
4
  python-dotenv
5
  APScheduler
6
  huggingface-hub
7
+ gradio
8
  pandas
9
+ flask
10
 
utils.py ADDED
@@ -0,0 +1,449 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import os
3
+ import math
4
+ import time
5
+ import json
6
+ import wandb
7
+ import pickle
8
+ import datetime
9
+ import argparse
10
+ import functools
11
+ import traceback
12
+
13
+ import pandas as pd
14
+ import numpy as np
15
+ import bittensor as bt
16
+
17
+ from dotenv import load_dotenv
18
+ from dataclasses import dataclass
19
+ from typing import Dict, List, Any, Optional, Tuple
20
+ from bittensor.extrinsics.serving import get_metadata
21
+
22
+
23
+ NETUID = 9
24
+ DELAY_SECS = 3
25
+ RETRIES = 1# 5
26
+
27
+ load_dotenv()
28
+
29
+ WANDB_TOKEN = os.environ.get("WANDB_API_KEY", None)
30
+ SUBTENSOR_ENDPOINT = os.environ.get("SUBTENSOR_ENDPOINT", None)
31
+ VALIDATOR_WANDB_PROJECT = "opentensor-dev/pretraining-subnet"
32
+ BENCHMARK_WANDB_PROJECT = "pretraining-benchmark-data"
33
+ BENCHMARK_FLAG = os.environ.get("BENCHMARK_FLAG", None)
34
+
35
+ @dataclass
36
+ class ModelData:
37
+ uid: int
38
+ hotkey: str
39
+ namespace: str
40
+ name: str
41
+ commit: str
42
+ hash: str
43
+ block: int
44
+ incentive: float
45
+ emission: float
46
+
47
+ @classmethod
48
+ def from_compressed_str(
49
+ cls,
50
+ uid: int,
51
+ hotkey: str,
52
+ cs: str,
53
+ block: int,
54
+ incentive: float,
55
+ emission: float,
56
+ ):
57
+ """Returns an instance of this class from a compressed string representation"""
58
+ tokens = cs.split(":")
59
+ return ModelData(
60
+ uid=uid,
61
+ hotkey=hotkey,
62
+ namespace=tokens[0],
63
+ name=tokens[1],
64
+ commit=tokens[2] if tokens[2] != "None" else None,
65
+ hash=tokens[3] if tokens[3] != "None" else None,
66
+ block=block,
67
+ incentive=incentive,
68
+ emission=emission,
69
+ )
70
+
71
+
72
+ def run_with_retries(func, *args, **kwargs):
73
+ for i in range(0, RETRIES):
74
+ try:
75
+ return func(*args, **kwargs)
76
+ except (Exception, RuntimeError):
77
+ bt.logging.error(f"Failed to run function: {traceback.format_exc()}")
78
+ if i == RETRIES - 1:
79
+ raise
80
+ time.sleep(DELAY_SECS)
81
+ raise RuntimeError("Should never happen")
82
+
83
+
84
+ def get_subtensor_and_metagraph() -> Tuple[bt.subtensor, bt.metagraph]:
85
+
86
+ def _internal() -> Tuple[bt.subtensor, bt.metagraph]:
87
+ if SUBTENSOR_ENDPOINT:
88
+ parser = argparse.ArgumentParser()
89
+ bt.subtensor.add_args(parser)
90
+ subtensor = bt.subtensor(config=bt.config(parser=parser, args=["--subtensor.chain_endpoint", SUBTENSOR_ENDPOINT]))
91
+ else:
92
+ subtensor = bt.subtensor("finney")
93
+
94
+ metagraph = subtensor.metagraph(NETUID, lite=False)
95
+
96
+ return subtensor, metagraph
97
+
98
+ return run_with_retries(_internal)
99
+
100
+
101
+ def get_subnet_data(
102
+ subtensor: bt.subtensor, metagraph: bt.metagraph
103
+ ) -> List[ModelData]:
104
+ result = []
105
+ for uid in metagraph.uids.tolist():
106
+ hotkey = metagraph.hotkeys[uid]
107
+ metadata = None
108
+ try:
109
+ metadata = run_with_retries(
110
+ functools.partial(get_metadata, subtensor, metagraph.netuid, hotkey)
111
+ )
112
+ except:
113
+ print(f"Failed to get metadata for UID {uid}: {traceback.format_exc()}")
114
+
115
+ if not metadata:
116
+ continue
117
+
118
+ commitment = metadata["info"]["fields"][0]
119
+ hex_data = commitment[list(commitment.keys())[0]][2:]
120
+ chain_str = bytes.fromhex(hex_data).decode()
121
+ block = metadata["block"]
122
+
123
+ incentive = np.nan_to_num(metagraph.incentive[uid]).item()
124
+ emission = (
125
+ np.nan_to_num(metagraph.emission[uid]).item() * 20
126
+ ) # convert to daily TAO
127
+
128
+ model_data = None
129
+ try:
130
+ model_data = ModelData.from_compressed_str(
131
+ uid, hotkey, chain_str, block, incentive, emission
132
+ )
133
+ except:
134
+ continue
135
+
136
+ result.append(model_data)
137
+ return result
138
+
139
+
140
+
141
+
142
+ def get_wandb_runs(project: str, filters: Dict[str, Any]) -> List:
143
+ """Get the latest runs from Wandb, retrying infinitely until we get them."""
144
+ while True:
145
+ api = wandb.Api(api_key=WANDB_TOKEN)
146
+ runs = list(
147
+ api.runs(
148
+ project,
149
+ filters=filters,
150
+ )
151
+ )
152
+ if len(runs) > 0:
153
+ return runs
154
+ # WandDB API is quite unreliable. Wait another minute and try again.
155
+ bt.logging.error("Failed to get runs from Wandb. Trying again in 60 seconds.")
156
+ time.sleep(60)
157
+
158
+
159
+ def get_scores(
160
+ uids: List[int],
161
+ wandb_runs: List,
162
+ ) -> Dict[int, Dict[str, Optional[float]]]:
163
+ result = {}
164
+ previous_timestamp = None
165
+ # Iterate through the runs until we've processed all the uids.
166
+ for i, run in enumerate(wandb_runs):
167
+ if not "original_format_json" in run.summary:
168
+ continue
169
+ data = json.loads(run.summary["original_format_json"])
170
+ all_uid_data = data["uid_data"]
171
+ timestamp = data["timestamp"]
172
+ # Make sure runs are indeed in descending time order.
173
+ #assert (
174
+ #previous_timestamp is None or timestamp < previous_timestamp
175
+ #), f"Timestamps are not in descending order: {timestamp} >= {previous_timestamp}"
176
+ previous_timestamp = timestamp
177
+
178
+ for uid in uids:
179
+ if uid in result:
180
+ continue
181
+ if str(uid) in all_uid_data:
182
+ uid_data = all_uid_data[str(uid)]
183
+ # Only the most recent run is fresh.
184
+ is_fresh = i == 0
185
+ result[uid] = {
186
+ "avg_loss": uid_data.get("average_loss", None),
187
+ "win_rate": uid_data.get("win_rate", None),
188
+ "win_total": uid_data.get("win_total", None),
189
+ "weight": uid_data.get("weight", None),
190
+ "fresh": is_fresh,
191
+ }
192
+ if len(result) == len(uids):
193
+ break
194
+ return result
195
+
196
+
197
+
198
+ def get_validator_weights(
199
+ metagraph: bt.metagraph,
200
+ ) -> Dict[int, Tuple[float, int, Dict[int, float]]]:
201
+ """Returns a dictionary of validator UIDs to (vtrust, stake, {uid: weight})."""
202
+ ret = {}
203
+ for uid in metagraph.uids.tolist():
204
+ vtrust = metagraph.validator_trust[uid].item()
205
+ stake = metagraph.stake[uid].item()
206
+ if vtrust > 0 and stake > 10_000:
207
+ ret[uid] = (vtrust, stake, {})
208
+ for ouid in metagraph.uids.tolist():
209
+ if ouid == uid:
210
+ continue
211
+ weight = round(metagraph.weights[uid][ouid].item(), 4)
212
+ if weight > 0:
213
+ ret[uid][-1][ouid] = weight
214
+ return ret
215
+
216
+
217
+
218
+
219
+ def get_losses_over_time(wandb_runs: List) -> pd.DataFrame:
220
+ """Returns a dataframe of the best average model loss over time."""
221
+ timestamps = []
222
+ best_losses = []
223
+
224
+ for run in wandb_runs:
225
+ if "original_format_json" not in run.summary:
226
+ continue
227
+ data = json.loads(run.summary["original_format_json"])
228
+ all_uid_data = data["uid_data"]
229
+ timestamp = datetime.datetime.fromtimestamp(data["timestamp"])
230
+ best_loss = math.inf
231
+ for _, uid_data in all_uid_data.items():
232
+ loss = uid_data.get("average_loss", math.inf)
233
+ # Filter out the numbers from the exploit and when validators lost the best model.
234
+ if loss < best_loss and (loss > 2.5 or timestamp > datetime.datetime(2024,2,12)) and (loss < 5 or timestamp > datetime.datetime(2024,3,27)):
235
+ best_loss = uid_data["average_loss"]
236
+ if best_loss != math.inf:
237
+ timestamps.append(timestamp)
238
+ best_losses.append(best_loss)
239
+
240
+ return pd.DataFrame({"timestamp": timestamps, "best_loss": best_losses})
241
+
242
+
243
+
244
+ def next_epoch(subtensor: bt.subtensor, block: int) -> int:
245
+ return (
246
+ block
247
+ + subtensor.get_subnet_hyperparameters(NETUID).tempo
248
+ - subtensor.blocks_since_epoch(NETUID, block)
249
+ )
250
+
251
+
252
+ def is_floatable(x) -> bool:
253
+ return (
254
+ isinstance(x, float) and not math.isnan(x) and not math.isinf(x)
255
+ ) or isinstance(x, int)
256
+
257
+
258
+
259
+ def format_score(uid: int, scores, key) -> Optional[float]:
260
+ if uid in scores:
261
+ if key in scores[uid]:
262
+ point = scores[uid][key]
263
+ if is_floatable(point):
264
+ return round(scores[uid][key], 4)
265
+ return None
266
+
267
+
268
+ def leaderboard_data(
269
+ leaderboard: List[ModelData],
270
+ scores: Dict[int, Dict[str, Optional[float]]],
271
+ show_stale: bool,
272
+ ) -> List[List[Any]]:
273
+ """Returns the leaderboard data, based on models data and UID scores."""
274
+ return [
275
+ [
276
+ f"[{c.namespace}/{c.name} ({c.commit[0:8]})](https://huggingface.co/{c.namespace}/{c.name}/commit/{c.commit})",
277
+ format_score(c.uid, scores, "win_rate"),
278
+ format_score(c.uid, scores, "avg_loss"),
279
+ format_score(c.uid, scores, "weight"),
280
+ c.uid,
281
+ c.block,
282
+ ]
283
+ for c in leaderboard
284
+ if (c.uid in scores and scores[c.uid]["fresh"]) or show_stale
285
+ ]
286
+
287
+
288
+ def get_benchmarks() -> Tuple[pd.DataFrame, datetime.datetime]:
289
+ """Returns the latest benchmarks and the time they were run."""
290
+ if not BENCHMARK_WANDB_PROJECT:
291
+ bt.logging.error("No benchmark project set.")
292
+ return None, None
293
+ runs = get_wandb_runs(project=BENCHMARK_WANDB_PROJECT, filters=None)
294
+ for run in runs:
295
+ artifacts = list(run.logged_artifacts())
296
+ if artifacts:
297
+ table = artifacts[-1].get("benchmarks")
298
+ if table:
299
+ return table.get_dataframe(), datetime.datetime.strptime(run.metadata["startedAt"], "%Y-%m-%dT%H:%M:%S.%f")
300
+ bt.logging.error("Failed to get benchmarks from Wandb.")
301
+ return None, None
302
+
303
+
304
+ def make_validator_dataframe(validator_df: pd.DataFrame, model_data: ModelData) -> pd.DataFrame:
305
+
306
+ values = [
307
+ [uid, int(validator_df[uid][1]), round(validator_df[uid][0], 4)]
308
+ + [
309
+ validator_df[uid][-1].get(c.uid)
310
+ for c in model_data
311
+ if c.incentive
312
+ ]
313
+ for uid, _ in sorted(
314
+ zip(
315
+ validator_df.keys(),
316
+ [validator_df[x][1] for x in validator_df.keys()],
317
+ ),
318
+ key=lambda x: x[1],
319
+ reverse=True,
320
+ )
321
+ ]
322
+ dtypes = {"UID":int, "Stake (τ)":float, "V-Trust":float}
323
+ dtypes.update({
324
+ f"{c.namespace}/{c.name} ({c.commit[0:8]})": float
325
+ for c in model_data
326
+ if c.incentive
327
+ })
328
+ return pd.DataFrame(values, columns=dtypes.keys()).astype(dtypes)
329
+
330
+ def make_metagraph_dataframe(metagraph: bt.metagraph, weights=False) -> pd.DataFrame:
331
+
332
+ cols = ['stake','emission','trust','validator_trust','dividends','incentive','R', 'consensus','validator_permit']
333
+
334
+ frame = pd.DataFrame({k: getattr(metagraph, k) for k in cols})
335
+ frame['block'] = metagraph.block.item()
336
+ frame['netuid'] = NETUID
337
+ frame['uid'] = range(len(frame))
338
+ frame['hotkey'] = [axon.hotkey for axon in metagraph.axons]
339
+ frame['coldkey'] = [axon.coldkey for axon in metagraph.axons]
340
+ if weights and metagraph.W is not None:
341
+ # convert NxN tensor to a list of lists so it fits into the dataframe
342
+ frame['weights'] = [w.tolist() for w in metagraph.W]
343
+
344
+ return frame
345
+
346
+ def load_state_vars() -> dict[Any]:
347
+ while True:
348
+ try:
349
+ subtensor, metagraph = get_subtensor_and_metagraph()
350
+
351
+ bt.logging.success("Loaded subtensor and metagraph")
352
+
353
+ model_data: List[ModelData] = get_subnet_data(subtensor, metagraph)
354
+ model_data.sort(key=lambda x: x.incentive, reverse=True)
355
+
356
+ bt.logging.success(f'Loaded {len(model_data)} models')
357
+ vali_runs = get_wandb_runs(project=VALIDATOR_WANDB_PROJECT, filters={"config.type": "validator", "config.uid": 238})
358
+
359
+ scores = get_scores([x.uid for x in model_data], vali_runs)
360
+
361
+ # TODO: Re-enable once ""SubtensorModule.BlocksSinceEpoch" not found" issue is resolved.
362
+ # current_block = metagraph.block.item()
363
+ # next_epoch_block = next_epoch(subtensor, current_block)
364
+
365
+ validator_df = get_validator_weights(metagraph)
366
+ weight_keys = set()
367
+ for uid, stats in validator_df.items():
368
+ weight_keys.update(stats[-1].keys())
369
+
370
+ # TODO: re-enable benchmarks
371
+ # Enable benchmark if the flag is set
372
+ if BENCHMARK_FLAG:
373
+ benchmarks, benchmark_timestamp = get_benchmarks()
374
+ else:
375
+ benchmarks, benchmark_timestamp = None, None
376
+ break
377
+
378
+ except KeyboardInterrupt:
379
+ bt.logging.error("Exiting...")
380
+ break
381
+
382
+ except Exception as e:
383
+ print(f"Failed to get data: {traceback.format_exc()}")
384
+ time.sleep(30)
385
+
386
+ return {
387
+ 'metagraph': metagraph,
388
+ "model_data": model_data,
389
+ "vali_runs": vali_runs,
390
+ "scores": scores,
391
+ "validator_df": validator_df,
392
+ "benchmarks": benchmarks,
393
+ "benchmark_timestamp": benchmark_timestamp
394
+ }
395
+
396
+ def test_load_state_vars():
397
+
398
+ subtensor = bt.subtensor("finney")
399
+ metagraph = subtensor.metagraph(NETUID, lite=True)
400
+ model_data = [
401
+ ModelData(uid=253, hotkey='5DjoPAgZ54Zf6NsuiVYh8RjonnWWWREE2iXBNzM2VDBMQDPm', namespace='jw-hf-test', name='jw2', commit='aad131f6b02219964e6dcf749c2a23e75a7ceca8', hash='L1ImYzWJwV+9KSnZ2TYW0Iy2KMcVjJVTd30YJoRkpbw=', block=3131103, incentive=1.0, emission=209.06051635742188),
402
+ ModelData(uid=1, hotkey='5CccVtjk4yamCao6QYgEg7jc8vktdj16RbLKNUftHfEsjuJS', namespace='borggAI', name='bittensor-subnet9-models', commit='d373864bc6c972872edb8db95eed570958054bac', hash='+drdTIKYEGYClW2FFVVID6A2Dh//4rLmExRFCJsH6Y4=', block=2081837, incentive=0.0, emission=0.0),
403
+ ModelData(uid=2, hotkey='5HYwoXaczs3jAptbb5mk4aUCkgZqeNcNzJKxSec97GwasfLy', namespace='jungiebeen', name='pretrain1', commit='4c0c6bfd0f92e243d6c8a82209142e7204c852c3', hash='ld/agc0XIWICom/Cpj0fkQLcMogMNj/F65MJogK5RLY=', block=2467482, incentive=0.0, emission=0.0),
404
+ ModelData(uid=3, hotkey='5Dnb6edh9yTeEp5aasRPZVPRAkxvQ6qnERVcXw22awMZ5rxm', namespace='jungiebeen', name='pretrain2', commit='e827b7281c92224adb11124489cc45356553a87a', hash='ld/agc0XIWICom/Cpj0fkQLcMogMNj/F65MJogK5RLY=', block=2467497, incentive=0.0, emission=0.0),
405
+ ModelData(uid=4, hotkey='5FRfca8NbnH424WaX43PMhKBnbLA1bZpRRoXXiVs6HgsxN4K', namespace='ZainAli60', name='mine_modeles', commit='8a4ed4ad1f1fb58d424fd22e8e9874b87d32917c', hash='tVcbZAFoNIOF+Ntxq31OQ2NrLXf5iFCmmPUJlpkMYYo=', block=2508509, incentive=0.0, emission=0.0)
406
+ ]
407
+ vali_runs = get_wandb_runs(project=VALIDATOR_WANDB_PROJECT, filters={"config.type": "validator", "config.uid": 238})
408
+
409
+ scores = get_scores([x.uid for x in model_data], vali_runs)
410
+
411
+ validator_df = {
412
+ 28: (1.0, 33273.4453125, {253: 1.0}),
413
+ 49: (0.9127794504165649,
414
+ 10401.677734375,
415
+ {7: 0.0867,
416
+ 217: 0.0001,
417
+ 219: 0.0001,
418
+ 241: 0.0001,
419
+ 248: 0.0001,
420
+ 253: 0.9128}),
421
+ 78: (1.0, 26730.37109375, {253: 1.0}),
422
+ 116: (1.0, 629248.4375, {253: 1.0}),
423
+ 150: (1.0, 272634.53125, {253: 1.0}),
424
+ 161: (1.0, 280212.53125, {253: 1.0}),
425
+ 180: (1.0, 16838.0, {253: 1.0}),
426
+ 184: (1.0, 47969.3984375, {253: 1.0}),
427
+ 210: (1.0, 262846.28125, {253: 1.0}),
428
+ 213: (1.0, 119462.734375, {253: 1.0}),
429
+ 215: (1.0, 274747.46875, {253: 1.0}),
430
+ 234: (1.0, 38831.6953125, {253: 1.0}),
431
+ 236: (1.0, 183966.9375, {253: 1.0}),
432
+ 238: (1.0, 1293707.25, {253: 1.0}),
433
+ 240: (1.0, 106461.6015625, {253: 1.0}),
434
+ 243: (1.0, 320271.5, {253: 1.0}),
435
+ 244: (1.0, 116138.9609375, {253: 1.0}),
436
+ 247: (0.9527428150177002, 119812.390625, {7: 0.0472, 253: 0.9528}),
437
+ 249: (1.0, 478127.3125, {253: 1.0}),
438
+ 252: (1.0, 442395.03125, {253: 1.0}),
439
+ 254: (1.0, 46845.2109375, {253: 1.0}),
440
+ 255: (1.0, 28977.56640625, {253: 1.0})
441
+ }
442
+
443
+ return {
444
+ 'metagraph': metagraph,
445
+ "model_data": model_data,
446
+ "vali_runs": vali_runs,
447
+ "scores": scores,
448
+ "validator_df": validator_df,
449
+ }