rusticluftig commited on
Commit
943df4d
1 Parent(s): ef2baab

Add leaderboard

Browse files
Files changed (3) hide show
  1. README.md +3 -3
  2. app.py +339 -0
  3. requirements.txt +7 -0
README.md CHANGED
@@ -1,10 +1,10 @@
1
  ---
2
  title: Pretraining Leaderboard
3
- emoji: 👁
4
  colorFrom: indigo
5
- colorTo: gray
6
  sdk: gradio
7
- sdk_version: 4.16.0
8
  app_file: app.py
9
  pinned: false
10
  ---
 
1
  ---
2
  title: Pretraining Leaderboard
3
+ emoji:
4
  colorFrom: indigo
5
+ colorTo: blue
6
  sdk: gradio
7
+ sdk_version: 3.41.0
8
  app_file: app.py
9
  pinned: false
10
  ---
app.py ADDED
@@ -0,0 +1,339 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import bittensor as bt
3
+ from typing import Dict, List, Any, Optional, Tuple
4
+ from bittensor.extrinsics.serving import get_metadata
5
+ from dataclasses import dataclass
6
+ import requests
7
+ import wandb
8
+ import math
9
+ import os
10
+ import datetime
11
+ import time
12
+ import json
13
+ import pandas as pd
14
+ from dotenv import load_dotenv
15
+ from huggingface_hub import HfApi
16
+ from apscheduler.schedulers.background import BackgroundScheduler
17
+
18
+ load_dotenv()
19
+
20
+ FONT = (
21
+ """<link href="https://fonts.cdnfonts.com/css/jmh-typewriter" rel="stylesheet">"""
22
+ )
23
+ TITLE = """<h1 align="center" id="space-title" class="typewriter">Subnet 9 Leaderboard</h1>"""
24
+ HEADER = """<h2 align="center" class="typewriter"><a href="https://github.com/RaoFoundation/pretraining" target="_blank">Subnet 9</a> is a <a href="https://bittensor.com/" target="_blank">Bittensor</a> subnet that rewards miners for producing pretrained Foundation-Models on the <a href="https://huggingface.co/datasets/tiiuae/falcon-refinedweb" target="_blank">Falcon Refined Web dataset</a>. It acts like a continuous benchmark whereby miners are rewarded for attaining the best losses on randomly sampled pages of Falcon.<br/>The models with the best head-to-head loss on the evaluation data receive a steady emission of TAO.</h3>"""
25
+ 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>."""
26
+ EVALUATION_HEADER = """<h3 align="center">Shows the latest internal evaluation statistics as calculated by the Opentensor validator</h3>"""
27
+ VALIDATOR_WANDB_PROJECT = "opentensor-dev/pretraining-subnet"
28
+ H4_TOKEN = os.environ.get("H4_TOKEN", None)
29
+ API = HfApi(token=H4_TOKEN)
30
+ WANDB_TOKEN = os.environ.get("WANDB_API_KEY", None)
31
+ REPO_ID = "RusticLuftig/9-leaderboard"
32
+ MAX_AVG_LOSS_POINTS = 1
33
+ RETRIES = 5
34
+ DELAY_SECS = 3
35
+ NETUID = 9
36
+ SECONDS_PER_BLOCK = 12
37
+
38
+
39
+ @dataclass
40
+ class ModelData:
41
+ uid: int
42
+ hotkey: str
43
+ namespace: str
44
+ name: str
45
+ commit: str
46
+ hash: str
47
+ block: int
48
+ incentive: float
49
+ emission: float
50
+
51
+ @classmethod
52
+ def from_compressed_str(
53
+ cls,
54
+ uid: int,
55
+ hotkey: str,
56
+ cs: str,
57
+ block: int,
58
+ incentive: float,
59
+ emission: float,
60
+ ):
61
+ """Returns an instance of this class from a compressed string representation"""
62
+ tokens = cs.split(":")
63
+ return ModelData(
64
+ uid=uid,
65
+ hotkey=hotkey,
66
+ namespace=tokens[0],
67
+ name=tokens[1],
68
+ commit=tokens[2] if tokens[2] != "None" else None,
69
+ hash=tokens[3] if tokens[3] != "None" else None,
70
+ block=block,
71
+ incentive=incentive,
72
+ emission=emission,
73
+ )
74
+
75
+
76
+ def run_with_retries(func, *args, **kwargs):
77
+ for i in range(0, RETRIES):
78
+ try:
79
+ return func(*args, **kwargs)
80
+ except:
81
+ if i == RETRIES - 1:
82
+ raise
83
+ time.sleep(DELAY_SECS)
84
+ raise RuntimeError("Should never happen")
85
+
86
+
87
+ def get_subtensor_and_metagraph() -> Tuple[bt.subtensor, bt.metagraph]:
88
+ def _internal() -> Tuple[bt.subtensor, bt.metagraph]:
89
+ subtensor = bt.subtensor("finney")
90
+ metagraph = bt.metagraph(NETUID, lite=False)
91
+ return subtensor, metagraph
92
+
93
+ return run_with_retries(_internal)
94
+
95
+
96
+ def get_tao_price() -> float:
97
+ return run_with_retries(
98
+ lambda: float(
99
+ requests.get(
100
+ "https://api.kucoin.com/api/v1/market/stats?symbol=TAO-USDT"
101
+ ).json()["data"]["last"]
102
+ )
103
+ )
104
+
105
+
106
+ def get_validator_weights(
107
+ metagraph: bt.metagraph,
108
+ ) -> Dict[int, Tuple[float, int, Dict[int, float]]]:
109
+ """Returns a dictionary of validator UIDs to (vtrust, stake, {uid: weight})."""
110
+ ret = {}
111
+ for uid in metagraph.uids.tolist():
112
+ vtrust = metagraph.validator_trust[uid].item()
113
+ if vtrust > 0:
114
+ ret[uid] = (vtrust, metagraph.S[uid].item(), {})
115
+ for ouid in metagraph.uids.tolist():
116
+ if ouid == uid:
117
+ continue
118
+ weight = round(metagraph.weights[uid][ouid].item(), 4)
119
+ if weight > 0:
120
+ ret[uid][-1][ouid] = weight
121
+ return ret
122
+
123
+
124
+ def get_subnet_data(
125
+ subtensor: bt.subtensor, metagraph: bt.metagraph
126
+ ) -> List[ModelData]:
127
+ result = []
128
+ for uid in metagraph.uids.tolist():
129
+ hotkey = metagraph.hotkeys[uid]
130
+ metadata = get_metadata(subtensor, metagraph.netuid, hotkey)
131
+ if not metadata:
132
+ continue
133
+
134
+ commitment = metadata["info"]["fields"][0]
135
+ hex_data = commitment[list(commitment.keys())[0]][2:]
136
+ chain_str = bytes.fromhex(hex_data).decode()
137
+ block = metadata["block"]
138
+ incentive = metagraph.incentive[uid].nan_to_num().item()
139
+ emission = (
140
+ metagraph.emission[uid].nan_to_num().item() * 20
141
+ ) # convert to daily TAO
142
+
143
+ model_data = None
144
+ try:
145
+ model_data = ModelData.from_compressed_str(
146
+ uid, hotkey, chain_str, block, incentive, emission
147
+ )
148
+ except:
149
+ continue
150
+
151
+ result.append(model_data)
152
+ return result
153
+
154
+
155
+ def is_floatable(x) -> bool:
156
+ return (
157
+ isinstance(x, float) and not math.isnan(x) and not math.isinf(x)
158
+ ) or isinstance(x, int)
159
+
160
+
161
+ def get_scores(
162
+ uids: List[int],
163
+ ) -> Dict[int, Dict[str, Optional[float]]]:
164
+ api = wandb.Api(api_key=WANDB_TOKEN)
165
+ runs = list(
166
+ api.runs(
167
+ VALIDATOR_WANDB_PROJECT,
168
+ filters={"config.type": "validator", "config.uid": 238},
169
+ )
170
+ )
171
+
172
+ result = {}
173
+ previous_timestamp = None
174
+ # Iterate through the runs until we've processed all the uids.
175
+ for i, run in enumerate(runs):
176
+ if not "original_format_json" in run.summary:
177
+ continue
178
+ data = json.loads(run.summary["original_format_json"])
179
+ all_uid_data = data["uid_data"]
180
+ timestamp = data["timestamp"]
181
+
182
+ # Make sure runs are indeed in descending time order.
183
+ assert (
184
+ previous_timestamp is None or timestamp < previous_timestamp
185
+ ), f"Timestamps are not in descending order: {timestamp} >= {previous_timestamp}"
186
+ previous_timestamp = timestamp
187
+
188
+ for uid in uids:
189
+ if uid in result:
190
+ continue
191
+ if str(uid) in all_uid_data:
192
+ uid_data = all_uid_data[str(uid)]
193
+ # Only the most recent run is fresh.
194
+ is_fresh = i == 0
195
+ result[uid] = {
196
+ "avg_loss": uid_data.get("average_loss", None),
197
+ "win_rate": uid_data.get("win_rate", None),
198
+ "win_total": uid_data.get("win_total", None),
199
+ "weight": uid_data.get("weight", None),
200
+ "fresh": is_fresh,
201
+ }
202
+ if len(result) == len(uids):
203
+ break
204
+ return result
205
+
206
+
207
+ def format_score(uid: int, scores, key) -> Optional[float]:
208
+ if uid in scores:
209
+ if key in scores[uid]:
210
+ point = scores[uid][key]
211
+ if is_floatable(point):
212
+ return round(scores[uid][key], 4)
213
+ return None
214
+
215
+
216
+ def next_epoch(subtensor: bt.subtensor, block: int) -> int:
217
+ return block + subtensor.get_subnet_hyperparameters(
218
+ NETUID
219
+ ).tempo - subtensor.blocks_since_epoch(NETUID, block)
220
+
221
+
222
+ def get_next_update_div(current_block: int, next_update_block: int) -> str:
223
+ now = datetime.datetime.now()
224
+ blocks_to_go = next_update_block - current_block
225
+ next_update_time = now + datetime.timedelta(
226
+ seconds=blocks_to_go * SECONDS_PER_BLOCK
227
+ )
228
+ delta = next_update_time - now
229
+ return f"""<div align="center" style="font-size: larger;">Next reward update: <b>{blocks_to_go}</b> blocks (~{int(delta.total_seconds() // 60)} minutes)</div>"""
230
+
231
+
232
+ def leaderboard_data(
233
+ leaderboard: List[ModelData],
234
+ scores: Dict[int, Dict[str, Optional[float]]],
235
+ show_stale: bool,
236
+ ) -> List[List[Any]]:
237
+ """Returns the leaderboard data, based on models data and UID scores."""
238
+ return [
239
+ [
240
+ f"[{c.namespace}/{c.name} ({c.commit[0:8]})](https://huggingface.co/{c.namespace}/{c.name}/commit/{c.commit})",
241
+ format_score(c.uid, scores, "win_rate"),
242
+ format_score(c.uid, scores, "avg_loss"),
243
+ format_score(c.uid, scores, "weight"),
244
+ c.uid,
245
+ c.block,
246
+ ]
247
+ for c in leaderboard
248
+ if (c.uid in scores and scores[c.uid]["fresh"]) or show_stale
249
+ ]
250
+
251
+ def restart_space():
252
+ API.restart_space(repo_id=REPO_ID, token=H4_TOKEN)
253
+
254
+
255
+ def main():
256
+ subtensor, metagraph = get_subtensor_and_metagraph()
257
+
258
+ tao_price = get_tao_price()
259
+
260
+ model_data: List[ModelData] = get_subnet_data(subtensor, metagraph)
261
+ model_data.sort(key=lambda x: x.incentive, reverse=True)
262
+
263
+ scores = get_scores([x.uid for x in model_data])
264
+
265
+ current_block = metagraph.block.item()
266
+ next_epoch_block = next_epoch(subtensor, current_block)
267
+
268
+ validator_df = get_validator_weights(metagraph)
269
+ weight_keys = set()
270
+ for uid, stats in validator_df.items():
271
+ weight_keys.update(stats[-1].keys())
272
+
273
+ demo = gr.Blocks(css=".typewriter {font-family: 'JMH Typewriter', sans-serif;}")
274
+ with demo:
275
+ gr.HTML(FONT)
276
+ gr.HTML(TITLE)
277
+ gr.HTML(HEADER)
278
+
279
+ gr.HTML(value=get_next_update_div(current_block, next_epoch_block))
280
+
281
+ gr.Label(
282
+ value={
283
+ f"{c.namespace}/{c.name} ({c.commit[0:8]}) · ${round(c.emission * tao_price, 2):,} (τ{round(c.emission, 2):,})": c.incentive
284
+ for c in model_data
285
+ if c.incentive
286
+ },
287
+ num_top_classes=10,
288
+ )
289
+
290
+ with gr.Accordion("Evaluation Stats"):
291
+ gr.HTML(EVALUATION_HEADER)
292
+ show_stale = gr.Checkbox(label="Show Stale", interactive=True)
293
+ leaderboard_table = gr.components.Dataframe(
294
+ value=leaderboard_data(model_data, scores, show_stale.value),
295
+ headers=["Name", "Win Rate", "Average Loss", "Weight", "UID", "Block"],
296
+ datatype=["markdown", "number", "number", "number", "number", "number"],
297
+ elem_id="leaderboard-table",
298
+ interactive=False,
299
+ visible=True,
300
+ )
301
+ gr.HTML(EVALUATION_DETAILS)
302
+ show_stale.change(lambda stale: leaderboard_data(model_data, scores, stale), inputs=[show_stale], outputs=leaderboard_table)
303
+
304
+ with gr.Accordion("Validator Stats"):
305
+ gr.components.Dataframe(
306
+ value=[
307
+ [uid, int(validator_df[uid][1]), round(validator_df[uid][0], 4)]
308
+ + [validator_df[uid][-1].get(c.uid) for c in model_data if c.incentive]
309
+ for uid, _ in sorted(
310
+ zip(
311
+ validator_df.keys(),
312
+ [validator_df[x][1] for x in validator_df.keys()],
313
+ ),
314
+ key=lambda x: x[1],
315
+ reverse=True,
316
+ )
317
+ ],
318
+ headers=["UID", "Stake (τ)", "V-Trust"]
319
+ + [
320
+ f"{c.namespace}/{c.name} ({c.commit[0:8]})"
321
+ for c in model_data
322
+ if c.incentive
323
+ ],
324
+ datatype=["number", "number", "number"]
325
+ + ["number" for c in model_data if c.incentive],
326
+ interactive=False,
327
+ visible=True,
328
+ )
329
+
330
+
331
+ scheduler = BackgroundScheduler()
332
+ scheduler.add_job(
333
+ restart_space, "interval", seconds=60 * 15
334
+ ) # restart every 15 minutes
335
+ scheduler.start()
336
+
337
+ demo.launch()
338
+
339
+ main()
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ bittensor==6.7.0
2
+ requests==2.31.0
3
+ wandb==0.16.2
4
+ python-dotenv==1.0.1
5
+ APScheduler==3.10.1
6
+ huggingface-hub>=0.18.0
7
+ pandas==2.2.0