dashboard / scripts /.ipynb_checkpoints /build_data-checkpoint.py
3v324v23's picture
first commit
b413fd8
Raw
History Blame Contribute Delete
7.65 kB
#!/usr/bin/env python3
"""Build the dashboard's public JSON payload from a W&B project."""
from __future__ import annotations
import argparse
import json
import math
import os
import sys
from collections import defaultdict
from datetime import datetime, timezone
_SCORE_PREFIX = "miner/"
_SCORE_SUFFIX = "/score"
_LEN_SUFFIX = "/completion_len"
_ORIG_SCORE = "original/score"
_ORIG_LEN = "original/completion_len"
def _miner_hotkey_from_score_key(key: str) -> str | None:
if key.startswith(_SCORE_PREFIX) and key.endswith(_SCORE_SUFFIX):
hk = key[len(_SCORE_PREFIX) : -len(_SCORE_SUFFIX)]
return hk or None
return None
def _as_float(value) -> float | None:
try:
f = float(value)
except (TypeError, ValueError):
return None
return f if math.isfinite(f) else None
def extract_rankings(summary: dict) -> list[dict]:
"""Return the current miner ranking from a run summary."""
out = []
for key, value in summary.items():
hk = _miner_hotkey_from_score_key(key)
if hk is None:
continue
score = _as_float(value)
if score is None:
continue
out.append({"hotkey": hk, "score": score})
out.sort(key=lambda r: r["score"], reverse=True)
return out
def extract_history(run) -> dict:
"""Return aligned per-epoch miner and base-model series for one run."""
rows_by_epoch: dict[float, dict] = {}
miner_keys: set[str] = set()
saw_orig_score = saw_orig_len = False
for row in run.scan_history():
epoch = _as_float(row.get("epoch"))
if epoch is None:
continue
bucket = rows_by_epoch.setdefault(epoch, {})
for key, value in row.items():
hk = _miner_hotkey_from_score_key(key)
if hk is not None:
bucket[("miner", hk, "score")] = _as_float(value)
miner_keys.add(hk)
elif key.startswith(_SCORE_PREFIX) and key.endswith(_LEN_SUFFIX):
hk2 = key[len(_SCORE_PREFIX) : -len(_LEN_SUFFIX)]
if hk2:
bucket[("miner", hk2, "completion_len")] = _as_float(value)
miner_keys.add(hk2)
elif key == _ORIG_SCORE:
bucket[("orig", "score")] = _as_float(value)
saw_orig_score = True
elif key == _ORIG_LEN:
bucket[("orig", "completion_len")] = _as_float(value)
saw_orig_len = True
epochs = sorted(rows_by_epoch)
miners: dict[str, dict] = {}
for hk in miner_keys:
miners[hk] = {
"score": [rows_by_epoch[e].get(("miner", hk, "score")) for e in epochs],
"completion_len": [rows_by_epoch[e].get(("miner", hk, "completion_len")) for e in epochs],
}
original = {}
if saw_orig_score:
original["score"] = [rows_by_epoch[e].get(("orig", "score")) for e in epochs]
if saw_orig_len:
original["completion_len"] = [rows_by_epoch[e].get(("orig", "completion_len")) for e in epochs]
return {"epochs": [int(e) if e == int(e) else e for e in epochs], "miners": miners, "original": original}
def build_aggregate(validators: list[dict]) -> dict:
"""Average miner scores across validators that ranked each miner."""
score_sums: dict[str, float] = defaultdict(float)
score_counts: dict[str, int] = defaultdict(int)
for v in validators:
for r in v["rankings"]:
score_sums[r["hotkey"]] += r["score"]
score_counts[r["hotkey"]] += 1
rankings = [
{"hotkey": hk, "score": score_sums[hk] / score_counts[hk], "validators": score_counts[hk]}
for hk in score_sums
]
rankings.sort(key=lambda r: r["score"], reverse=True)
history = {"epochs": [], "miners": {}, "original": {}}
if rankings:
top = rankings[0]["hotkey"]
history = _mean_history(validators, top)
return {"rankings": rankings, "history": history}
def _mean_history(validators: list[dict], top_hotkey: str) -> dict:
"""Average per-epoch history for the leading miner and base model."""
epochs = sorted({e for v in validators for e in v["history"].get("epochs", [])})
def mean_series(getter):
out = []
for e in epochs:
vals = []
for v in validators:
hist = v["history"]
if e not in hist.get("epochs", []):
continue
pos = hist["epochs"].index(e)
val = getter(hist, pos)
if val is not None and val == val:
vals.append(val)
out.append(sum(vals) / len(vals) if vals else None)
return out
miner_score = mean_series(lambda h, p: (h["miners"].get(top_hotkey, {}).get("score") or [None] * (p + 1))[p] if h["miners"].get(top_hotkey) else None)
miner_len = mean_series(lambda h, p: (h["miners"].get(top_hotkey, {}).get("completion_len") or [None] * (p + 1))[p] if h["miners"].get(top_hotkey) else None)
orig_score = mean_series(lambda h, p: (h["original"].get("score") or [None] * (p + 1))[p] if h["original"].get("score") else None)
orig_len = mean_series(lambda h, p: (h["original"].get("completion_len") or [None] * (p + 1))[p] if h["original"].get("completion_len") else None)
original = {}
if any(x is not None for x in orig_score):
original["score"] = orig_score
if any(x is not None for x in orig_len):
original["completion_len"] = orig_len
return {
"epochs": epochs,
"miners": {top_hotkey: {"score": miner_score, "completion_len": miner_len}},
"original": original,
}
def build(project: str, entity: str | None, api=None) -> dict:
if api is None:
import wandb
api = wandb.Api()
path = f"{entity}/{project}" if entity else project
validators: list[dict] = []
seen: set[str] = set()
for run in api.runs(path, order="-created_at"):
hotkey = dict(run.config).get("validator_hotkey")
if not isinstance(hotkey, str) or not hotkey or hotkey in seen:
continue
seen.add(hotkey)
validators.append(
{
"hotkey": hotkey,
"rankings": extract_rankings(dict(run.summary)),
"history": extract_history(run),
}
)
return {
"generated_at": datetime.now(timezone.utc).isoformat(),
"validators": validators,
"aggregate": build_aggregate(validators),
}
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description="Generate the static dashboard's data.json from wandb.")
parser.add_argument("--wandb-project", required=True, help="Shared wandb project every validator logs into")
parser.add_argument("--wandb-entity", default=None, help="Wandb entity/team (default: the API key's default entity)")
parser.add_argument("--out", default="data.json", help="Output path for the generated JSON")
args = parser.parse_args(argv)
key = os.environ.get("WANDB_KEY") or os.environ.get("WANDB_API_KEY")
if not key:
print("error: set WANDB_KEY (or WANDB_API_KEY) in the environment", file=sys.stderr)
return 2
os.environ["WANDB_API_KEY"] = key
data = build(args.wandb_project, args.wandb_entity)
with open(args.out, "w", encoding="utf-8") as fh:
json.dump(data, fh, indent=2)
fh.write("\n")
print(f"wrote {args.out}: {len(data['validators'])} validators, "
f"{len(data['aggregate']['rankings'])} ranked miners")
return 0
if __name__ == "__main__":
raise SystemExit(main())