Spaces:
Running
Running
from typing import Callable | |
import gradio as gr | |
import asyncio | |
import time | |
import threading | |
from src.retrieve_data import ( | |
get_gpus_for_leaderboard, | |
get_leaderboard_names, | |
get_leaderboard_submissions, | |
get_submission_count, | |
) | |
from src.envs import CACHE_TIMEOUT, BACKGROUND_REFRESH_INTERVAL | |
# key: func_name:args:kwargs, value: (timestamp, data) | |
cache = {} | |
active_selections = { | |
"leaderboard": None, | |
"gpu": None, | |
} | |
loop = asyncio.new_event_loop() | |
asyncio.set_event_loop(loop) | |
background_refresh_running = True | |
def cached_fetch( | |
func: Callable, *args, force_refresh=False, limit=None, offset=0, **kwargs | |
): | |
"""Fetch data with caching to avoid redundant API calls""" | |
cache_key = ( | |
f"{func.__name__}:{str(args)}:{str(kwargs)}:limit={limit}:offset={offset}" | |
) | |
current_time = time.time() | |
if not force_refresh and cache_key in cache: | |
timestamp, data = cache[cache_key] | |
if current_time - timestamp < CACHE_TIMEOUT: | |
return data | |
print(f"Fetching {cache_key}") | |
if func.__name__ == "get_leaderboard_submissions": | |
result = loop.run_until_complete( | |
func(*args, limit=limit, offset=offset, **kwargs) | |
) | |
else: | |
result = loop.run_until_complete(func(*args, **kwargs)) | |
cache[cache_key] = (current_time, result) | |
return result | |
def invalidate_cache(prefix=None): | |
"""Invalidate all cache entries or those matching a prefix""" | |
global cache | |
if prefix is None: | |
cache = {} | |
else: | |
cache = {k: v for k, v in cache.items() if not k.startswith(prefix)} | |
def background_refresh(): | |
"""Background thread to refresh active data periodically""" | |
while background_refresh_running: | |
try: | |
time.sleep(BACKGROUND_REFRESH_INTERVAL) | |
lb_name = active_selections["leaderboard"] | |
gpu_name = active_selections["gpu"] | |
if lb_name and gpu_name: | |
cached_fetch( | |
get_leaderboard_submissions, lb_name, gpu_name, force_refresh=True | |
) | |
cached_fetch(get_gpus_for_leaderboard, lb_name, force_refresh=True) | |
cached_fetch(get_leaderboard_names, force_refresh=True) | |
except Exception as e: | |
print(f"Background refresh error: {e}") | |
def build_ui(): | |
# Define the function first before using it | |
def create_table_for_lb_with_global_rank(lb_data, offset): | |
"""Create table with global ranks instead of page-specific ranks""" | |
headers = [ | |
"Rank", | |
"User Name", | |
"Submission ID", | |
"Submission Name", | |
"Runtime (ms)", | |
"Submission Date", | |
] | |
rows = [] | |
for i, result in enumerate(lb_data.results, 1): | |
# Calculate global rank by adding offset | |
global_rank = i + offset | |
# Only show medals for the top 3 overall and only on the first page | |
if offset == 0 and global_rank <= 3: # first page and top 3 | |
if global_rank == 1: | |
rank_display = "๐ฅ 1" | |
elif global_rank == 2: | |
rank_display = "๐ฅ 2" | |
elif global_rank == 3: | |
rank_display = "๐ฅ 3" | |
else: | |
rank_display = str(global_rank) | |
score = float(result.submission_score) * 1000 | |
rows.append( | |
[ | |
rank_display, | |
result.user_name, | |
str(result.submission_id), # Add submission ID as a new column | |
result.submission_name, | |
f"{score:.4f}", | |
result.submission_time.strftime("%Y-%m-%d %H:%M:%S"), | |
] | |
) | |
# Apply different class based on whether it's the first page or not | |
elem_classes = "" if offset == 0 else "non-first-page-table" | |
df = gr.Dataframe( | |
headers=headers, | |
datatype=[ | |
"str", | |
"str", | |
"str", # Submission ID | |
"str", | |
"str", | |
"timestamp", | |
], | |
value=rows, | |
interactive=False, | |
elem_classes=elem_classes, | |
) | |
return df | |
with gr.Blocks( | |
title="ML Leaderboards", | |
theme=gr.themes.Soft(), | |
css=""" | |
/* Apply medal colors to all tables by default */ | |
.gradio-container table tr:nth-child(1) { | |
background-color: rgba(255, 215, 0, 0.2) !important; /* Gold */ | |
} | |
.gradio-container table tr:nth-child(2) { | |
background-color: rgba(192, 192, 192, 0.2) !important; /* Silver */ | |
} | |
.gradio-container table tr:nth-child(3) { | |
background-color: rgba(205, 127, 50, 0.2) !important; /* Bronze */ | |
} | |
/* Remove medal colors for non-first pages */ | |
.non-first-page-table tr:nth-child(1), | |
.non-first-page-table tr:nth-child(2), | |
.non-first-page-table tr:nth-child(3) { | |
background-color: inherit !important; | |
} | |
.pagination-controls { | |
display: flex; | |
justify-content: space-between; | |
align-items: center; | |
margin-top: 10px; | |
width: 100%; | |
} | |
.pagination-info { | |
text-align: center; | |
flex-grow: 1; | |
} | |
.pagination-button { | |
min-width: 100px; | |
} | |
""", | |
) as app: | |
gr.Markdown("# ๐ฟ KernelBot Leaderboard ๐ฟ") | |
lb_names = cached_fetch(get_leaderboard_names) | |
selected_lb = lb_names[0] | |
gpu_names = cached_fetch(get_gpus_for_leaderboard, selected_lb) | |
selected_gpu = gpu_names[0] | |
# Set default pagination values | |
items_per_page = 10 | |
current_page = 1 | |
data = cached_fetch( | |
get_leaderboard_submissions, | |
selected_lb, | |
selected_gpu, | |
limit=items_per_page, | |
offset=0, | |
) | |
total_count = cached_fetch(get_submission_count, selected_lb, selected_gpu) | |
total_pages = (total_count + items_per_page - 1) // items_per_page | |
with gr.Row(): | |
with gr.Column(scale=1): | |
lb_dropdown = gr.Dropdown( | |
choices=lb_names, | |
label="Select Leaderboard", | |
interactive=True, | |
value=selected_lb, | |
) | |
gpu_dropdown = gr.Dropdown( | |
choices=gpu_names, | |
label="Select GPU", | |
interactive=True, | |
value=selected_gpu, | |
) | |
with gr.Row(): | |
# Initial table is first page | |
results_table = create_table_for_lb_with_global_rank(data, 0) | |
with gr.Row(elem_classes="pagination-controls"): | |
with gr.Column(scale=1, min_width=100, elem_classes="pagination-button"): | |
prev_btn = gr.Button("โ Previous", interactive=(current_page > 1)) | |
with gr.Column(scale=2, elem_classes="pagination-info"): | |
page_info = gr.Markdown(f"Page {current_page} of {total_pages}") | |
with gr.Column(scale=1, min_width=100, elem_classes="pagination-button"): | |
next_btn = gr.Button("Next โ", interactive=(current_page < total_pages)) | |
def on_lb_change(lb_name): | |
gpu_choices = cached_fetch(get_gpus_for_leaderboard, lb_name) | |
active_selections["leaderboard"] = lb_name | |
if gpu_choices: | |
active_selections["gpu"] = gpu_choices[0] | |
# Reset to page 1 when changing leaderboard | |
data = cached_fetch( | |
get_leaderboard_submissions, | |
lb_name, | |
gpu_choices[0] if gpu_choices else None, | |
limit=items_per_page, | |
offset=0, | |
) | |
# Get total count for pagination | |
total_count = cached_fetch( | |
get_submission_count, lb_name, gpu_choices[0] if gpu_choices else None | |
) | |
total_pages = (total_count + items_per_page - 1) // items_per_page | |
return ( | |
gr.update( | |
choices=gpu_choices, value=gpu_choices[0] if gpu_choices else None | |
), | |
create_table_for_lb_with_global_rank(data, 0), | |
gr.update(value=f"Page 1 of {total_pages}"), | |
gr.update(interactive=False), # prev button disabled on page 1 | |
gr.update( | |
interactive=(total_pages > 1) | |
), # next button enabled if more than 1 page | |
) | |
def update_table(lb_name, gpu_name, page=1): | |
if not gpu_name: | |
return None, gr.update(), gr.update(), gr.update() | |
active_selections["gpu"] = gpu_name | |
offset = (page - 1) * items_per_page | |
data = cached_fetch( | |
get_leaderboard_submissions, | |
lb_name, | |
gpu_name, | |
limit=items_per_page, | |
offset=offset, | |
) | |
# Get total count for pagination | |
total_count = cached_fetch(get_submission_count, lb_name, gpu_name) | |
total_pages = (total_count + items_per_page - 1) // items_per_page | |
# Create table with global ranks | |
table = create_table_for_lb_with_global_rank(data, offset) | |
return ( | |
table, | |
gr.update(value=f"Page {page} of {total_pages}"), | |
gr.update(interactive=(page > 1)), | |
gr.update(interactive=(page < total_pages)), | |
) | |
def next_page(): | |
nonlocal current_page | |
lb_name = active_selections["leaderboard"] | |
gpu_name = active_selections["gpu"] | |
# Get total count to check if we can go to next page | |
total_count = cached_fetch(get_submission_count, lb_name, gpu_name) | |
total_pages = (total_count + items_per_page - 1) // items_per_page | |
if current_page < total_pages: | |
current_page += 1 | |
return update_table(lb_name, gpu_name, current_page) | |
return update_table(lb_name, gpu_name, current_page) | |
def prev_page(): | |
nonlocal current_page | |
if current_page > 1: | |
current_page -= 1 | |
lb_name = active_selections["leaderboard"] | |
gpu_name = active_selections["gpu"] | |
return update_table(lb_name, gpu_name, current_page) | |
lb_dropdown.change( | |
fn=on_lb_change, | |
inputs=[lb_dropdown], | |
outputs=[gpu_dropdown, results_table, page_info, prev_btn, next_btn], | |
) | |
gpu_dropdown.change( | |
fn=lambda lb, gpu: update_table(lb, gpu, 1), # Reset to page 1 | |
inputs=[lb_dropdown, gpu_dropdown], | |
outputs=[results_table, page_info, prev_btn, next_btn], | |
) | |
next_btn.click( | |
fn=next_page, | |
inputs=[], | |
outputs=[results_table, page_info, prev_btn, next_btn], | |
) | |
prev_btn.click( | |
fn=prev_page, | |
inputs=[], | |
outputs=[results_table, page_info, prev_btn, next_btn], | |
) | |
return app | |
if __name__ == "__main__": | |
try: | |
background_thread = threading.Thread(target=background_refresh, daemon=True) | |
background_thread.start() | |
app = build_ui() | |
app.launch() | |
finally: | |
background_refresh_running = False | |
background_thread.join(timeout=1.0) | |
loop.close() | |