CodeSoft commited on
Commit
f9e82ab
Β·
verified Β·
1 Parent(s): c9ee5f8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +167 -129
app.py CHANGED
@@ -1,5 +1,4 @@
1
  import spaces
2
-
3
  import os
4
  import json
5
  import random
@@ -41,6 +40,20 @@ MODEL_DISPLAY: Dict[str, str] = {
41
  "HuggingFaceTB/SmolLM2-135M-Instruct": "SmolLM2-135M-Instruct",
42
  }
43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  FALLBACK_IDS: Dict[str, str] = {}
45
 
46
  INIT_RATING = 1000
@@ -74,11 +87,11 @@ def get_data_dir() -> Path:
74
  pass
75
  return local
76
 
77
- def get_elo_file() -> Path:
78
- return get_data_dir() / "elo.json"
79
 
80
- def get_chat_file() -> Path:
81
- return get_data_dir() / "chats.jsonl"
82
 
83
  # Keep legacy globals for backwards compat (now dynamic via functions)
84
  DATA_DIR = get_data_dir()
@@ -90,6 +103,10 @@ GEN_DEFAULTS: Dict[str, dict] = {
90
  "SupraLabs/Supra2-100M-Instruct": {"max_new_tokens": 64, "temperature": 0.7, "top_p": 0.9, "top_k": 25, "repetition_penalty": 1.1, "do_sample": True, "no_repeat_ngram_size": 3},
91
  "BananaMind/BananaMind-2-Medium-Chat": {"max_new_tokens": 64, "temperature": 0.7, "top_p": 0.9, "repetition_penalty": 1.1, "do_sample": True},
92
  "CodeSoft/MetaDiffusion-150M-ChatBase": {"max_new_tokens": 96, "num_steps": 128, "temperature": 0.7, "top_p": 0.9, "repetition_penalty": 1.5},
 
 
 
 
93
  }
94
 
95
  MODEL_CONTEXT: Dict[str, int] = {
@@ -97,11 +114,30 @@ MODEL_CONTEXT: Dict[str, int] = {
97
  "SupraLabs/Supra2-100M-Instruct": 1024,
98
  "BananaMind/BananaMind-2-Medium-Chat": 3072,
99
  "CodeSoft/MetaDiffusion-150M-ChatBase": 5120,
 
 
 
 
100
  }
101
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  # ZeroGPU: CUDA is emulated at startup so models load onto cuda at module level;
103
  # real GPU is only mounted inside @spaces.GPU-decorated calls.
104
- DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
105
 
106
  @dataclass
107
  class MetaDiffusionConfig:
@@ -396,15 +432,15 @@ def _diff_generate_response(model, tokenizer, prompt_ids, gen_len, num_steps, te
396
  # ---------------------------------------------------------------------------
397
  # ELO persistence
398
  # ---------------------------------------------------------------------------
399
- def init_elo_state() -> Dict[str, dict]:
400
- return {mid: {"rating": float(INIT_RATING), "wins": 0, "losses": 0, "battles": 0, "ties": 0, "both_bad": 0} for mid in MODEL_IDS}
401
 
402
- def load_elo() -> Dict[str, dict]:
403
- if get_elo_file().exists():
404
  try:
405
- with open(get_elo_file(), "r") as f:
406
  data = json.load(f)
407
- for mid in MODEL_IDS:
408
  if mid not in data:
409
  data[mid] = {"rating": float(INIT_RATING), "wins": 0, "losses": 0, "battles": 0, "ties": 0, "both_bad": 0}
410
  else:
@@ -417,12 +453,12 @@ def load_elo() -> Dict[str, dict]:
417
  return data
418
  except Exception as e:
419
  logger.warning(f"Failed to load ELO file: {e}, resetting")
420
- return init_elo_state()
421
 
422
- def save_elo(state: Dict[str, dict]):
423
  try:
424
  get_data_dir().mkdir(parents=True, exist_ok=True)
425
- with open(get_elo_file(), "w") as f:
426
  json.dump(state, f, indent=2)
427
  except Exception as e:
428
  logger.error(f"Failed to save ELO: {e}")
@@ -430,7 +466,7 @@ def save_elo(state: Dict[str, dict]):
430
  def expected_score(ra: float, rb: float) -> float:
431
  return 1.0 / (1.0 + BASE ** ((rb - ra) / SCALE))
432
 
433
- def update_elo(state: Dict[str, dict], model_a: str, model_b: str, winner: Optional[str]) -> Dict[str, dict]:
434
  if model_a not in state or model_b not in state:
435
  logger.warning(f"Unknown models in ELO update: {model_a}, {model_b}")
436
  return state
@@ -465,17 +501,17 @@ def update_elo(state: Dict[str, dict], model_a: str, model_b: str, winner: Optio
465
  state[model_a]["both_bad"] = state[model_a].get("both_bad", 0) + 1
466
  state[model_b]["both_bad"] = state[model_b].get("both_bad", 0) + 1
467
 
468
- save_elo(state)
469
  return state
470
 
471
- def leaderboard_dataframe(state: Optional[Dict[str, dict]] = None) -> pd.DataFrame:
472
  if state is None:
473
- state = load_elo()
474
  rows = []
475
- for mid in MODEL_IDS:
476
  info = state.get(mid, {"rating": INIT_RATING, "wins": 0, "losses": 0, "battles": 0, "ties": 0})
477
  rows.append({
478
- "Model": MODEL_DISPLAY.get(mid, mid),
479
  "Model ID": mid,
480
  "ELO": round(float(info["rating"]), 1),
481
  "Battles": int(info["battles"]),
@@ -492,7 +528,7 @@ def leaderboard_dataframe(state: Optional[Dict[str, dict]] = None) -> pd.DataFra
492
  # ---------------------------------------------------------------------------
493
  # Chat logging to data/chats.jsonl
494
  # ---------------------------------------------------------------------------
495
- def log_battle(prompt: str, model_a: str, model_b: str, response_a: str, response_b: str, chosen: str, winner_model: str):
496
  """
497
  Append one battle record to data/chats.jsonl.
498
  Fields: prompt, response_a, response_b, model_a, model_b, chosen (A/B/tie/both_bad), winner_model, timestamp
@@ -511,7 +547,7 @@ def log_battle(prompt: str, model_a: str, model_b: str, response_a: str, respons
511
  "winner_model": winner_model,
512
  "chosen_response": response_a if chosen == "A" else response_b if chosen == "B" else "",
513
  }
514
- with open(get_chat_file(), "a", encoding="utf-8") as f:
515
  f.write(json.dumps(record, ensure_ascii=False) + "\n")
516
  except Exception as e:
517
  logger.error(f"Failed to log battle: {e}")
@@ -584,18 +620,18 @@ def load_models():
584
  global models, tokenizers, model_load_errors
585
  # If already populated (including diffusion manual), return
586
  # But we want to ensure all 5 attempted
587
- if models and len(models) >= 3:
588
  # Already loaded, but ensure diffusion tried
589
  if "CodeSoft/MetaDiffusion-150M-ChatBase" not in models:
590
  load_diffusion_manual()
591
  return models, tokenizers
592
 
593
- logger.info(f"Loading {len(MODEL_IDS)} models on {DEVICE} ...")
594
  # Try diffusion manual first (bypass HF Auto which fails on unknown type)
595
  if "CodeSoft/MetaDiffusion-150M-ChatBase" not in models:
596
  load_diffusion_manual()
597
 
598
- for mid in MODEL_IDS:
599
  if mid in models:
600
  continue # already loaded (diffusion)
601
  load_id = LOCAL_PATHS.get(mid, mid) if os.path.exists(LOCAL_PATHS.get(mid, "")) else mid
@@ -773,22 +809,24 @@ CSS = """
773
  .gradio-container {max-width: 1450px !important; width: 95% !important;}
774
  .vote-btn {font-weight: 700 !important;}
775
  /* Leaderboard: prevent ELO wrapping, give it fixed width */
776
- #leaderboard { overflow-x: auto; }
777
- #leaderboard table { table-layout: auto; width: 100%; }
778
- #leaderboard th:nth-child(4), #leaderboard td:nth-child(4) {
 
779
  min-width: 95px;
780
  width: 95px;
781
  white-space: nowrap;
782
  text-align: center;
783
  font-variant-numeric: tabular-nums;
784
  }
785
- #leaderboard th:nth-child(1), #leaderboard td:nth-child(1) { min-width: 55px; width: 55px; text-align: center; }
786
- #leaderboard td { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
 
787
  """
788
 
789
- def pick_random_pair(exclude_pair: Optional[Tuple[str, str]] = None) -> Tuple[str, str]:
790
- state = load_elo()
791
- models_list = MODEL_IDS[:]
792
  weights = []
793
  C = 5
794
  K = 100
@@ -801,13 +839,10 @@ def pick_random_pair(exclude_pair: Optional[Tuple[str, str]] = None) -> Tuple[st
801
  remaining_weights = [w for m, w in zip(models_list, weights) if m != a]
802
  b = random.choices(remaining, weights=remaining_weights, k=1)[0]
803
  if exclude_pair and set((a, b)) == set(exclude_pair):
804
- a, b = random.sample(MODEL_IDS, 2)
805
  return a, b
806
 
807
  def create_demo() -> gr.Blocks:
808
- state_init = load_elo()
809
- df_init = leaderboard_dataframe(state_init)
810
-
811
  with gr.Blocks(title="SLM Arena") as demo:
812
  gr.Markdown(
813
  """
@@ -815,10 +850,8 @@ def create_demo() -> gr.Blocks:
815
  """
816
  )
817
 
818
- last_pair = gr.State(None)
819
-
820
- with gr.Tabs():
821
- with gr.Tab("Arena", id=0):
822
  prompt = gr.Textbox(
823
  label="Your prompt",
824
  placeholder="Ask anything... e.g. 'Explain quantum computing in simple terms' or 'Write a haiku about rain'",
@@ -855,25 +888,53 @@ def create_demo() -> gr.Blocks:
855
  voted_state = gr.State(False)
856
  prompt_state = gr.State("")
857
 
858
- leaderboard_tab = gr.Tab("Leaderboard", id=1)
859
- with leaderboard_tab:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
860
  gr.Markdown("### πŸ† ELO Leaderboard")
861
  leaderboard = gr.Dataframe(
862
- value=df_init,
863
  headers=["Rank", "Model", "Model ID", "ELO", "Battles", "Wins", "Losses", "Ties", "Both Bad"],
864
  datatype=["number", "str", "str", "number", "number", "number", "number", "number", "number"],
865
  interactive=False,
866
  wrap=False,
867
  column_widths=["5%", "15%", "25%", "12%", "7%", "7%", "7%", "7%", "7%"],
868
- elem_id="leaderboard",
869
  )
870
- with gr.Row():
871
- refresh_btn = gr.Button("πŸ”„ Refresh", variant="secondary")
 
 
 
 
 
 
 
872
 
873
  # -------------------------------------------------------------------
874
  # Event handlers
875
  # -------------------------------------------------------------------
876
- def on_submit(user_prompt: str, last_pair_val):
877
  user_prompt = (user_prompt or "").strip()
878
  if not user_prompt:
879
  return (
@@ -888,9 +949,9 @@ def create_demo() -> gr.Blocks:
888
  gr.update(interactive=False),
889
  gr.update(visible=False),
890
  "", "", False, user_prompt, last_pair_val,
891
- leaderboard_dataframe(load_elo())
892
  )
893
- a, b = pick_random_pair(exclude_pair=last_pair_val)
894
  if random.random() < 0.5:
895
  a, b = b, a
896
  ensure_models_loaded()
@@ -912,10 +973,10 @@ def create_demo() -> gr.Blocks:
912
  gr.update(interactive=True),
913
  gr.update(visible=False),
914
  a, b, False, user_prompt, (a, b),
915
- leaderboard_dataframe(load_elo())
916
  )
917
 
918
- def on_vote(choice: str, model_a: str, model_b: str, resp_a: str, resp_b: str, user_prompt: str, voted: bool):
919
  if voted or not model_a or not model_b:
920
  return (
921
  gr.update(visible=False),
@@ -927,7 +988,7 @@ def create_demo() -> gr.Blocks:
927
  gr.update(interactive=False),
928
  gr.update(visible=False),
929
  voted,
930
- leaderboard_dataframe(load_elo())
931
  )
932
  if choice == "A":
933
  winner = model_a
@@ -949,37 +1010,37 @@ def create_demo() -> gr.Blocks:
949
  winner = model_b
950
  win_label = "B"
951
  chosen = "B"
952
- state = load_elo()
953
  ra_before = state[model_a]["rating"]
954
  rb_before = state[model_b]["rating"]
955
- update_elo(state, model_a, model_b, winner)
956
  ra_after = state[model_a]["rating"]
957
  rb_after = state[model_b]["rating"]
958
  delta_a = ra_after - ra_before
959
  delta_b = rb_after - rb_before
960
- reveal_a_text = f"**Model A:** `{model_a}` ({MODEL_DISPLAY.get(model_a, model_a)}) β€” ELO {ra_after:.1f} ({delta_a:+.1f})"
961
- reveal_b_text = f"**Model B:** `{model_b}` ({MODEL_DISPLAY.get(model_b, model_b)}) β€” ELO {rb_after:.1f} ({delta_b:+.1f})"
962
  if choice == "Tie":
963
  status_text = (
964
  f"You voted **Tie**: no winner\n\n"
965
- f"**ELO update:** {MODEL_DISPLAY.get(model_a, model_a)} {ra_before:.1f} β†’ {ra_after:.1f} ({delta_a:+.1f}) | "
966
- f"{MODEL_DISPLAY.get(model_b, model_b)} {rb_before:.1f} β†’ {rb_after:.1f} ({delta_b:+.1f})"
967
  )
968
  elif choice == "Both Bad":
969
  status_text = (
970
  f"You voted **Both Bad**: no winner\n\n"
971
- f"**ELO update:** {MODEL_DISPLAY.get(model_a, model_a)} {ra_before:.1f} β†’ {ra_after:.1f} ({delta_a:+.1f}) | "
972
- f"{MODEL_DISPLAY.get(model_b, model_b)} {rb_before:.1f} β†’ {rb_after:.1f} ({delta_b:+.1f})"
973
  )
974
  else:
975
  status_text = (
976
  f"You voted **{win_label}**: the winner is `{winner}`\n\n"
977
- f"**ELO update:** {MODEL_DISPLAY.get(model_a, model_a)} {ra_before:.1f} β†’ {ra_after:.1f} ({delta_a:+.1f}) | "
978
- f"{MODEL_DISPLAY.get(model_b, model_b)} {rb_before:.1f} β†’ {rb_after:.1f} ({delta_b:+.1f})"
979
  )
980
  # Log chat to data/chats.jsonl
981
- log_battle(user_prompt, model_a, model_b, resp_a, resp_b, chosen, winner)
982
- df = leaderboard_dataframe(state)
983
  return (
984
  gr.update(value=reveal_a_text, visible=True),
985
  gr.update(value=reveal_b_text, visible=True),
@@ -1024,67 +1085,40 @@ def create_demo() -> gr.Blocks:
1024
  "", "", False, ""
1025
  )
1026
 
1027
- def on_refresh():
1028
- return leaderboard_dataframe(load_elo())
1029
-
1030
- submit_btn.click(
1031
- fn=on_submit,
1032
- inputs=[prompt, last_pair],
1033
- outputs=[response_a, response_b, reveal_a, reveal_b, status, vote_a, vote_tie, vote_both_bad, vote_b, new_round_btn, model_a_state, model_b_state, voted_state, prompt_state, last_pair, leaderboard],
1034
- )
1035
 
1036
- prompt.submit(
1037
- fn=on_submit,
1038
- inputs=[prompt, last_pair],
1039
- outputs=[response_a, response_b, reveal_a, reveal_b, status, vote_a, vote_tie, vote_both_bad, vote_b, new_round_btn, model_a_state, model_b_state, voted_state, prompt_state, last_pair, leaderboard],
1040
- )
1041
 
1042
- vote_a.click(
1043
- fn=lambda ma, mb, ra, rb, pr, vd: on_vote("A", ma, mb, ra, rb, pr, vd),
1044
- inputs=[model_a_state, model_b_state, response_a, response_b, prompt_state, voted_state],
1045
- outputs=[reveal_a, reveal_b, status, vote_a, vote_tie, vote_both_bad, vote_b, new_round_btn, voted_state, leaderboard],
1046
- )
1047
- vote_tie.click(
1048
- fn=lambda ma, mb, ra, rb, pr, vd: on_vote("Tie", ma, mb, ra, rb, pr, vd),
1049
- inputs=[model_a_state, model_b_state, response_a, response_b, prompt_state, voted_state],
1050
- outputs=[reveal_a, reveal_b, status, vote_a, vote_tie, vote_both_bad, vote_b, new_round_btn, voted_state, leaderboard],
1051
- )
1052
- vote_both_bad.click(
1053
- fn=lambda ma, mb, ra, rb, pr, vd: on_vote("Both Bad", ma, mb, ra, rb, pr, vd),
1054
- inputs=[model_a_state, model_b_state, response_a, response_b, prompt_state, voted_state],
1055
- outputs=[reveal_a, reveal_b, status, vote_a, vote_tie, vote_both_bad, vote_b, new_round_btn, voted_state, leaderboard],
1056
- )
1057
- vote_b.click(
1058
- fn=lambda ma, mb, ra, rb, pr, vd: on_vote("B", ma, mb, ra, rb, pr, vd),
1059
- inputs=[model_a_state, model_b_state, response_a, response_b, prompt_state, voted_state],
1060
- outputs=[reveal_a, reveal_b, status, vote_a, vote_tie, vote_both_bad, vote_b, new_round_btn, voted_state, leaderboard],
1061
- )
1062
-
1063
- new_round_btn.click(
1064
- fn=on_new_round,
1065
- inputs=[],
1066
- outputs=[response_a, response_b, reveal_a, reveal_b, status, vote_a, vote_tie, vote_both_bad, vote_b, new_round_btn, model_a_state, model_b_state, voted_state, prompt_state],
1067
- )
1068
- clear_btn.click(
1069
- fn=on_clear,
1070
- inputs=[],
1071
- outputs=[prompt, response_a, response_b, reveal_a, reveal_b, status, vote_a, vote_tie, vote_both_bad, vote_b, new_round_btn, model_a_state, model_b_state, voted_state, prompt_state],
1072
- )
1073
-
1074
- refresh_btn.click(fn=on_refresh, inputs=[], outputs=[leaderboard])
1075
 
1076
- # Refresh when Leaderboard tab is selected (fixes stale df_init)
1077
- # Also refresh on page load but without global spinner (demo.load caused "loading..." until refresh when bucket slow)
1078
  try:
1079
- leaderboard_tab.select(fn=on_refresh, inputs=[], outputs=[leaderboard])
1080
  except Exception:
1081
  pass
1082
- # Page-load refresh without blocking UI (hidden progress)
1083
- try:
1084
- demo.load(fn=on_refresh, inputs=[], outputs=[leaderboard], show_progress="hidden")
1085
- except Exception:
1086
- # Fallback: no page-load auto-refresh, rely on tab select + initial df_init (now dynamic via get_data_dir)
1087
- pass
1088
 
1089
  return demo
1090
 
@@ -1093,8 +1127,8 @@ def create_demo() -> gr.Blocks:
1093
  # ---------------------------------------------------------------------------
1094
  if __name__ == "__main__":
1095
  print("=" * 60)
1096
- print("SLM Arena starting, attempting to load 4 models...")
1097
- print(f"Models: {MODEL_IDS}")
1098
  print(f"Data dir: {get_data_dir().resolve()} (bucket /data if mounted)")
1099
  print("=" * 60)
1100
  try:
@@ -1102,12 +1136,16 @@ if __name__ == "__main__":
1102
  except Exception as e:
1103
  logger.error(f"Model loading encountered error: {e}")
1104
  try:
1105
- df = leaderboard_dataframe(load_elo())
1106
- print(df.to_string(index=False))
1107
- print(f"\nChat log: {get_chat_file().resolve()} (exists={get_chat_file().exists()})")
1108
- if get_chat_file().exists():
1109
- with open(get_chat_file()) as f:
1110
- lines = sum(1 for _ in f)
 
 
 
 
1111
  print(f"Previous battles logged: {lines}")
1112
  except Exception as e:
1113
  logger.warning(f"Leaderboard preview failed: {e}")
 
1
  import spaces
 
2
  import os
3
  import json
4
  import random
 
40
  "HuggingFaceTB/SmolLM2-135M-Instruct": "SmolLM2-135M-Instruct",
41
  }
42
 
43
+ BASE_MODEL_IDS: List[str] = [
44
+ "fromziro/Zero-v0.1-150M",
45
+ "AxiomicLabs/GPT-X2.5-135M",
46
+ "BananaMind/BananaMind-2-Pro",
47
+ "HuggingFaceTB/SmolLM2-135M",
48
+ ]
49
+
50
+ BASE_MODEL_DISPLAY: Dict[str, str] = {
51
+ "fromziro/Zero-v0.1-150M": "Zero-v0.1-150M",
52
+ "AxiomicLabs/GPT-X2.5-135M": "GPT-X2.5-135M",
53
+ "BananaMind/BananaMind-2-Pro": "BananaMind-2-Pro",
54
+ "HuggingFaceTB/SmolLM2-135M": "SmolLM2-135M",
55
+ }
56
+
57
  FALLBACK_IDS: Dict[str, str] = {}
58
 
59
  INIT_RATING = 1000
 
87
  pass
88
  return local
89
 
90
+ def get_elo_file(name: str = "elo") -> Path:
91
+ return get_data_dir() / f"{name}.json"
92
 
93
+ def get_chat_file(name: str = "chats") -> Path:
94
+ return get_data_dir() / f"{name}.jsonl"
95
 
96
  # Keep legacy globals for backwards compat (now dynamic via functions)
97
  DATA_DIR = get_data_dir()
 
103
  "SupraLabs/Supra2-100M-Instruct": {"max_new_tokens": 64, "temperature": 0.7, "top_p": 0.9, "top_k": 25, "repetition_penalty": 1.1, "do_sample": True, "no_repeat_ngram_size": 3},
104
  "BananaMind/BananaMind-2-Medium-Chat": {"max_new_tokens": 64, "temperature": 0.7, "top_p": 0.9, "repetition_penalty": 1.1, "do_sample": True},
105
  "CodeSoft/MetaDiffusion-150M-ChatBase": {"max_new_tokens": 96, "num_steps": 128, "temperature": 0.7, "top_p": 0.9, "repetition_penalty": 1.5},
106
+ "fromziro/Zero-v0.1-150M": {"max_new_tokens": 64, "temperature": 0.8, "top_p": 0.95, "repetition_penalty": 1.1, "do_sample": True},
107
+ "AxiomicLabs/GPT-X2.5-135M": {"max_new_tokens": 64, "temperature": 0.8, "top_p": 0.95, "repetition_penalty": 1.1, "do_sample": True},
108
+ "BananaMind/BananaMind-2-Pro": {"max_new_tokens": 64, "temperature": 0.8, "top_p": 0.95, "repetition_penalty": 1.1, "do_sample": True},
109
+ "HuggingFaceTB/SmolLM2-135M": {"max_new_tokens": 64, "temperature": 0.8, "top_p": 0.95, "repetition_penalty": 1.1, "do_sample": True},
110
  }
111
 
112
  MODEL_CONTEXT: Dict[str, int] = {
 
114
  "SupraLabs/Supra2-100M-Instruct": 1024,
115
  "BananaMind/BananaMind-2-Medium-Chat": 3072,
116
  "CodeSoft/MetaDiffusion-150M-ChatBase": 5120,
117
+ "fromziro/Zero-v0.1-150M": 2048,
118
+ "AxiomicLabs/GPT-X2.5-135M": 2048,
119
+ "BananaMind/BananaMind-2-Pro": 3072,
120
+ "HuggingFaceTB/SmolLM2-135M": 2048,
121
  }
122
 
123
+
124
+ @dataclass
125
+ class ArenaSpec:
126
+ key: str
127
+ model_ids: List[str]
128
+ display: Dict[str, str]
129
+ elo_name: str
130
+ chat_name: str
131
+ arena_title: str
132
+ lb_title: str
133
+
134
+
135
+ MAIN_ARENA = ArenaSpec("main", MODEL_IDS, MODEL_DISPLAY, "elo", "chats", "Arena", "Leaderboard")
136
+ BASE_ARENA = ArenaSpec("base", BASE_MODEL_IDS, BASE_MODEL_DISPLAY, "base_elo", "base_chats", "Base Arena", "Base Leaderboard")
137
+
138
  # ZeroGPU: CUDA is emulated at startup so models load onto cuda at module level;
139
  # real GPU is only mounted inside @spaces.GPU-decorated calls.
140
+ DEVICE = os.environ.get("SLM_ARENA_DEVICE", "") or ("cuda" if torch.cuda.is_available() else "cpu")
141
 
142
  @dataclass
143
  class MetaDiffusionConfig:
 
432
  # ---------------------------------------------------------------------------
433
  # ELO persistence
434
  # ---------------------------------------------------------------------------
435
+ def init_elo_state(spec: ArenaSpec) -> Dict[str, dict]:
436
+ return {mid: {"rating": float(INIT_RATING), "wins": 0, "losses": 0, "battles": 0, "ties": 0, "both_bad": 0} for mid in spec.model_ids}
437
 
438
+ def load_elo(spec: ArenaSpec) -> Dict[str, dict]:
439
+ if get_elo_file(spec.elo_name).exists():
440
  try:
441
+ with open(get_elo_file(spec.elo_name), "r") as f:
442
  data = json.load(f)
443
+ for mid in spec.model_ids:
444
  if mid not in data:
445
  data[mid] = {"rating": float(INIT_RATING), "wins": 0, "losses": 0, "battles": 0, "ties": 0, "both_bad": 0}
446
  else:
 
453
  return data
454
  except Exception as e:
455
  logger.warning(f"Failed to load ELO file: {e}, resetting")
456
+ return init_elo_state(spec)
457
 
458
+ def save_elo(state: Dict[str, dict], spec: ArenaSpec):
459
  try:
460
  get_data_dir().mkdir(parents=True, exist_ok=True)
461
+ with open(get_elo_file(spec.elo_name), "w") as f:
462
  json.dump(state, f, indent=2)
463
  except Exception as e:
464
  logger.error(f"Failed to save ELO: {e}")
 
466
  def expected_score(ra: float, rb: float) -> float:
467
  return 1.0 / (1.0 + BASE ** ((rb - ra) / SCALE))
468
 
469
+ def update_elo(state: Dict[str, dict], model_a: str, model_b: str, winner: Optional[str], spec: ArenaSpec) -> Dict[str, dict]:
470
  if model_a not in state or model_b not in state:
471
  logger.warning(f"Unknown models in ELO update: {model_a}, {model_b}")
472
  return state
 
501
  state[model_a]["both_bad"] = state[model_a].get("both_bad", 0) + 1
502
  state[model_b]["both_bad"] = state[model_b].get("both_bad", 0) + 1
503
 
504
+ save_elo(state, spec)
505
  return state
506
 
507
+ def leaderboard_dataframe(state: Optional[Dict[str, dict]] = None, spec: ArenaSpec = MAIN_ARENA) -> pd.DataFrame:
508
  if state is None:
509
+ state = load_elo(spec)
510
  rows = []
511
+ for mid in spec.model_ids:
512
  info = state.get(mid, {"rating": INIT_RATING, "wins": 0, "losses": 0, "battles": 0, "ties": 0})
513
  rows.append({
514
+ "Model": spec.display.get(mid, mid),
515
  "Model ID": mid,
516
  "ELO": round(float(info["rating"]), 1),
517
  "Battles": int(info["battles"]),
 
528
  # ---------------------------------------------------------------------------
529
  # Chat logging to data/chats.jsonl
530
  # ---------------------------------------------------------------------------
531
+ def log_battle(spec: ArenaSpec, prompt: str, model_a: str, model_b: str, response_a: str, response_b: str, chosen: str, winner_model: str):
532
  """
533
  Append one battle record to data/chats.jsonl.
534
  Fields: prompt, response_a, response_b, model_a, model_b, chosen (A/B/tie/both_bad), winner_model, timestamp
 
547
  "winner_model": winner_model,
548
  "chosen_response": response_a if chosen == "A" else response_b if chosen == "B" else "",
549
  }
550
+ with open(get_chat_file(spec.chat_name), "a", encoding="utf-8") as f:
551
  f.write(json.dumps(record, ensure_ascii=False) + "\n")
552
  except Exception as e:
553
  logger.error(f"Failed to log battle: {e}")
 
620
  global models, tokenizers, model_load_errors
621
  # If already populated (including diffusion manual), return
622
  # But we want to ensure all 5 attempted
623
+ if models and len(models) >= len(MODEL_IDS) + len(BASE_MODEL_IDS) - 1:
624
  # Already loaded, but ensure diffusion tried
625
  if "CodeSoft/MetaDiffusion-150M-ChatBase" not in models:
626
  load_diffusion_manual()
627
  return models, tokenizers
628
 
629
+ logger.info(f"Loading {len(MODEL_IDS) + len(BASE_MODEL_IDS)} models on {DEVICE} ...")
630
  # Try diffusion manual first (bypass HF Auto which fails on unknown type)
631
  if "CodeSoft/MetaDiffusion-150M-ChatBase" not in models:
632
  load_diffusion_manual()
633
 
634
+ for mid in MODEL_IDS + BASE_MODEL_IDS:
635
  if mid in models:
636
  continue # already loaded (diffusion)
637
  load_id = LOCAL_PATHS.get(mid, mid) if os.path.exists(LOCAL_PATHS.get(mid, "")) else mid
 
809
  .gradio-container {max-width: 1450px !important; width: 95% !important;}
810
  .vote-btn {font-weight: 700 !important;}
811
  /* Leaderboard: prevent ELO wrapping, give it fixed width */
812
+ #leaderboard, #base_leaderboard { overflow-x: auto; }
813
+ #leaderboard table, #base_leaderboard table { table-layout: auto; width: 100%; }
814
+ #leaderboard th:nth-child(4), #leaderboard td:nth-child(4),
815
+ #base_leaderboard th:nth-child(4), #base_leaderboard td:nth-child(4) {
816
  min-width: 95px;
817
  width: 95px;
818
  white-space: nowrap;
819
  text-align: center;
820
  font-variant-numeric: tabular-nums;
821
  }
822
+ #leaderboard th:nth-child(1), #leaderboard td:nth-child(1),
823
+ #base_leaderboard th:nth-child(1), #base_leaderboard td:nth-child(1) { min-width: 55px; width: 55px; text-align: center; }
824
+ #leaderboard td, #base_leaderboard td { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
825
  """
826
 
827
+ def pick_random_pair(spec: ArenaSpec, exclude_pair: Optional[Tuple[str, str]] = None) -> Tuple[str, str]:
828
+ state = load_elo(spec)
829
+ models_list = spec.model_ids[:]
830
  weights = []
831
  C = 5
832
  K = 100
 
839
  remaining_weights = [w for m, w in zip(models_list, weights) if m != a]
840
  b = random.choices(remaining, weights=remaining_weights, k=1)[0]
841
  if exclude_pair and set((a, b)) == set(exclude_pair):
842
+ a, b = random.sample(spec.model_ids, 2)
843
  return a, b
844
 
845
  def create_demo() -> gr.Blocks:
 
 
 
846
  with gr.Blocks(title="SLM Arena") as demo:
847
  gr.Markdown(
848
  """
 
850
  """
851
  )
852
 
853
+ def make_arena_tab(spec: ArenaSpec) -> dict:
854
+ with gr.Tab(spec.arena_title, id=0 if spec.key == "main" else 2):
 
 
855
  prompt = gr.Textbox(
856
  label="Your prompt",
857
  placeholder="Ask anything... e.g. 'Explain quantum computing in simple terms' or 'Write a haiku about rain'",
 
888
  voted_state = gr.State(False)
889
  prompt_state = gr.State("")
890
 
891
+ return {
892
+ "prompt": prompt,
893
+ "submit_btn": submit_btn,
894
+ "clear_btn": clear_btn,
895
+ "response_a": response_a,
896
+ "response_b": response_b,
897
+ "reveal_a": reveal_a,
898
+ "reveal_b": reveal_b,
899
+ "vote_a": vote_a,
900
+ "vote_tie": vote_tie,
901
+ "vote_both_bad": vote_both_bad,
902
+ "vote_b": vote_b,
903
+ "status": status,
904
+ "new_round_btn": new_round_btn,
905
+ "model_a_state": model_a_state,
906
+ "model_b_state": model_b_state,
907
+ "voted_state": voted_state,
908
+ "prompt_state": prompt_state,
909
+ "last_pair": gr.State(None),
910
+ }
911
+
912
+ def make_leaderboard_tab(spec: ArenaSpec, elem_id: str, tab_id: int) -> dict:
913
+ with gr.Tab(spec.lb_title, id=tab_id) as tab:
914
  gr.Markdown("### πŸ† ELO Leaderboard")
915
  leaderboard = gr.Dataframe(
916
+ value=leaderboard_dataframe(load_elo(spec), spec),
917
  headers=["Rank", "Model", "Model ID", "ELO", "Battles", "Wins", "Losses", "Ties", "Both Bad"],
918
  datatype=["number", "str", "str", "number", "number", "number", "number", "number", "number"],
919
  interactive=False,
920
  wrap=False,
921
  column_widths=["5%", "15%", "25%", "12%", "7%", "7%", "7%", "7%", "7%"],
922
+ elem_id=elem_id,
923
  )
924
+ refresh_btn = gr.Button("πŸ”„ Refresh", variant="secondary")
925
+ return {"tab": tab, "leaderboard": leaderboard, "refresh_btn": refresh_btn}
926
+
927
+ with gr.Tabs():
928
+ ui = {}
929
+ lb = {}
930
+ for spec, lb_elem, lb_tab in ((MAIN_ARENA, "leaderboard", 1), (BASE_ARENA, "base_leaderboard", 3)):
931
+ ui[spec.key] = make_arena_tab(spec)
932
+ lb[spec.key] = make_leaderboard_tab(spec, lb_elem, lb_tab)
933
 
934
  # -------------------------------------------------------------------
935
  # Event handlers
936
  # -------------------------------------------------------------------
937
+ def on_submit(spec: ArenaSpec, user_prompt: str, last_pair_val):
938
  user_prompt = (user_prompt or "").strip()
939
  if not user_prompt:
940
  return (
 
949
  gr.update(interactive=False),
950
  gr.update(visible=False),
951
  "", "", False, user_prompt, last_pair_val,
952
+ leaderboard_dataframe(load_elo(spec), spec)
953
  )
954
+ a, b = pick_random_pair(spec, exclude_pair=last_pair_val)
955
  if random.random() < 0.5:
956
  a, b = b, a
957
  ensure_models_loaded()
 
973
  gr.update(interactive=True),
974
  gr.update(visible=False),
975
  a, b, False, user_prompt, (a, b),
976
+ leaderboard_dataframe(load_elo(spec), spec)
977
  )
978
 
979
+ def on_vote(spec: ArenaSpec, choice: str, model_a: str, model_b: str, resp_a: str, resp_b: str, user_prompt: str, voted: bool):
980
  if voted or not model_a or not model_b:
981
  return (
982
  gr.update(visible=False),
 
988
  gr.update(interactive=False),
989
  gr.update(visible=False),
990
  voted,
991
+ leaderboard_dataframe(load_elo(spec), spec)
992
  )
993
  if choice == "A":
994
  winner = model_a
 
1010
  winner = model_b
1011
  win_label = "B"
1012
  chosen = "B"
1013
+ state = load_elo(spec)
1014
  ra_before = state[model_a]["rating"]
1015
  rb_before = state[model_b]["rating"]
1016
+ update_elo(state, model_a, model_b, winner, spec)
1017
  ra_after = state[model_a]["rating"]
1018
  rb_after = state[model_b]["rating"]
1019
  delta_a = ra_after - ra_before
1020
  delta_b = rb_after - rb_before
1021
+ reveal_a_text = f"**Model A:** `{model_a}` ({spec.display.get(model_a, model_a)}) β€” ELO {ra_after:.1f} ({delta_a:+.1f})"
1022
+ reveal_b_text = f"**Model B:** `{model_b}` ({spec.display.get(model_b, model_b)}) β€” ELO {rb_after:.1f} ({delta_b:+.1f})"
1023
  if choice == "Tie":
1024
  status_text = (
1025
  f"You voted **Tie**: no winner\n\n"
1026
+ f"**ELO update:** {spec.display.get(model_a, model_a)} {ra_before:.1f} β†’ {ra_after:.1f} ({delta_a:+.1f}) | "
1027
+ f"{spec.display.get(model_b, model_b)} {rb_before:.1f} β†’ {rb_after:.1f} ({delta_b:+.1f})"
1028
  )
1029
  elif choice == "Both Bad":
1030
  status_text = (
1031
  f"You voted **Both Bad**: no winner\n\n"
1032
+ f"**ELO update:** {spec.display.get(model_a, model_a)} {ra_before:.1f} β†’ {ra_after:.1f} ({delta_a:+.1f}) | "
1033
+ f"{spec.display.get(model_b, model_b)} {rb_before:.1f} β†’ {rb_after:.1f} ({delta_b:+.1f})"
1034
  )
1035
  else:
1036
  status_text = (
1037
  f"You voted **{win_label}**: the winner is `{winner}`\n\n"
1038
+ f"**ELO update:** {spec.display.get(model_a, model_a)} {ra_before:.1f} β†’ {ra_after:.1f} ({delta_a:+.1f}) | "
1039
+ f"{spec.display.get(model_b, model_b)} {rb_before:.1f} β†’ {rb_after:.1f} ({delta_b:+.1f})"
1040
  )
1041
  # Log chat to data/chats.jsonl
1042
+ log_battle(spec, user_prompt, model_a, model_b, resp_a, resp_b, chosen, winner)
1043
+ df = leaderboard_dataframe(state, spec)
1044
  return (
1045
  gr.update(value=reveal_a_text, visible=True),
1046
  gr.update(value=reveal_b_text, visible=True),
 
1085
  "", "", False, ""
1086
  )
1087
 
1088
+ def on_refresh(spec: ArenaSpec):
1089
+ return leaderboard_dataframe(load_elo(spec), spec)
 
 
 
 
 
 
1090
 
1091
+ for spec in (MAIN_ARENA, BASE_ARENA):
1092
+ u, b = ui[spec.key], lb[spec.key]
1093
+ round_outputs = [u["response_a"], u["response_b"], u["reveal_a"], u["reveal_b"], u["status"], u["vote_a"], u["vote_tie"], u["vote_both_bad"], u["vote_b"], u["new_round_btn"], u["model_a_state"], u["model_b_state"], u["voted_state"], u["prompt_state"]]
1094
+ submit_outputs = round_outputs + [u["last_pair"], b["leaderboard"]]
1095
+ vote_outputs = [u["reveal_a"], u["reveal_b"], u["status"], u["vote_a"], u["vote_tie"], u["vote_both_bad"], u["vote_b"], u["new_round_btn"], u["voted_state"], b["leaderboard"]]
1096
 
1097
+ u["submit_btn"].click(
1098
+ fn=lambda p, lp, s=spec: on_submit(s, p, lp),
1099
+ inputs=[u["prompt"], u["last_pair"]],
1100
+ outputs=submit_outputs,
1101
+ )
1102
+ u["prompt"].submit(
1103
+ fn=lambda p, lp, s=spec: on_submit(s, p, lp),
1104
+ inputs=[u["prompt"], u["last_pair"]],
1105
+ outputs=submit_outputs,
1106
+ )
1107
+ for btn, choice in ((u["vote_a"], "A"), (u["vote_tie"], "Tie"), (u["vote_both_bad"], "Both Bad"), (u["vote_b"], "B")):
1108
+ btn.click(
1109
+ fn=lambda ma, mb, ra, rb, pr, vd, c=choice, s=spec: on_vote(s, c, ma, mb, ra, rb, pr, vd),
1110
+ inputs=[u["model_a_state"], u["model_b_state"], u["response_a"], u["response_b"], u["prompt_state"], u["voted_state"]],
1111
+ outputs=vote_outputs,
1112
+ )
1113
+ u["new_round_btn"].click(fn=on_new_round, inputs=[], outputs=round_outputs)
1114
+ u["clear_btn"].click(fn=on_clear, inputs=[], outputs=[u["prompt"]] + round_outputs)
1115
+ b["refresh_btn"].click(fn=lambda s=spec: on_refresh(s), inputs=[], outputs=[b["leaderboard"]])
1116
+ b["tab"].select(fn=lambda s=spec: on_refresh(s), inputs=[], outputs=[b["leaderboard"]])
 
 
 
 
 
 
 
 
 
 
 
 
 
1117
 
 
 
1118
  try:
1119
+ demo.load(fn=lambda: on_refresh(MAIN_ARENA), inputs=[], outputs=[lb["main"]["leaderboard"]], show_progress="hidden")
1120
  except Exception:
1121
  pass
 
 
 
 
 
 
1122
 
1123
  return demo
1124
 
 
1127
  # ---------------------------------------------------------------------------
1128
  if __name__ == "__main__":
1129
  print("=" * 60)
1130
+ print(f"SLM Arena starting, attempting to load {len(MODEL_IDS) + len(BASE_MODEL_IDS)} models on {DEVICE}...")
1131
+ print(f"Models: {MODEL_IDS + BASE_MODEL_IDS}")
1132
  print(f"Data dir: {get_data_dir().resolve()} (bucket /data if mounted)")
1133
  print("=" * 60)
1134
  try:
 
1136
  except Exception as e:
1137
  logger.error(f"Model loading encountered error: {e}")
1138
  try:
1139
+ for spec in (MAIN_ARENA, BASE_ARENA):
1140
+ df = leaderboard_dataframe(load_elo(spec), spec)
1141
+ print(df.to_string(index=False))
1142
+ chat_path = get_chat_file(spec.chat_name)
1143
+ print(f"\nChat log: {chat_path.resolve()} (exists={chat_path.exists()})")
1144
+ if chat_path.exists():
1145
+ with open(chat_path) as f:
1146
+ lines = sum(1 for _ in f)
1147
+ else:
1148
+ lines = 0
1149
  print(f"Previous battles logged: {lines}")
1150
  except Exception as e:
1151
  logger.warning(f"Leaderboard preview failed: {e}")