1+ import asyncio
12import json
23import subprocess
34import tempfile
1920)
2021from kernelbot .env import env
2122from kernelbot .ui .misc import ConfirmationView , DeleteConfirmationModal , GPUSelectionView
22- from libkernelbot .consts import GitHubGPU , ModalGPU
23+ from libkernelbot .consts import GitHubGPU , ModalGPU , SubmissionMode , get_gpu_by_name
2324from libkernelbot .leaderboard_db import LeaderboardDoesNotExist , LeaderboardItem , SubmissionItem
25+ from libkernelbot .submission import compute_score
2426from libkernelbot .task import LeaderboardDefinition , make_task_definition
2527from libkernelbot .utils import (
2628 KernelBotError ,
@@ -122,6 +124,10 @@ def __init__(self, bot: "ClusterBot"):
122124 name = "set-forum-ids" , description = "Sets forum IDs"
123125 )(self .set_forum_ids )
124126
127+ self .trigger_milestones = bot .admin_group .command (
128+ name = "trigger-milestones" , description = "Trigger running of milestones"
129+ )(self .trigger_milestones )
130+
125131 self ._scheduled_cleanup_temp_users .start ()
126132
127133 # --------------------------------------------------------------------------
@@ -162,6 +168,7 @@ async def leaderboard_create_local(
162168 interaction : discord .Interaction ,
163169 directory : str ,
164170 gpu : Optional [app_commands .Choice [str ]],
171+ milestones : Optional [bool ] = False ,
165172 ):
166173 is_admin = await self .admin_check (interaction )
167174 if not is_admin :
@@ -180,20 +187,19 @@ async def leaderboard_create_local(
180187 leaderboard_name = directory .name + "-dev"
181188
182189 # create-local overwrites existing leaderboard
190+ forum_channel = self .bot .get_channel (self .bot .leaderboard_forum_id )
191+ forum_thread = None
192+
183193 with self .bot .leaderboard_db as db :
184194 try :
185195 old_lb = db .get_leaderboard (leaderboard_name )
196+ forum_id = old_lb ["forum_id" ]
197+ forum_thread = await self .bot .fetch_channel (forum_id )
186198 except LeaderboardDoesNotExist :
187199 pass
188200 db .delete_leaderboard (leaderboard_name , force = True )
189201
190- # get existing forum thread or create new one
191- forum_channel = self .bot .get_channel (self .bot .leaderboard_forum_id )
192- forum_thread = None
193- if old_lb :
194- forum_id = old_lb ["forum_id" ]
195- forum_thread = await self .bot .fetch_channel (forum_id )
196-
202+ # create new forum thread if none exists
197203 if forum_thread is None :
198204 forum_thread = await forum_channel .create_thread (
199205 name = leaderboard_name ,
@@ -216,6 +222,11 @@ async def leaderboard_create_local(
216222 interaction ,
217223 f"Leaderboard '{ leaderboard_name } ' created." ,
218224 )
225+ else :
226+ raise KernelBotError (f"Error creating leaderboard '{ leaderboard_name } '" )
227+
228+ if milestones :
229+ await self ._submit_milestones (interaction , leaderboard_name )
219230
220231 def _parse_deadline (self , deadline : str ):
221232 # Try parsing with time first
@@ -354,14 +365,23 @@ async def create_leaderboard_in_db(
354365
355366 with self .bot .leaderboard_db as db :
356367 try :
357- db .create_leaderboard (
368+ lb_id = db .create_leaderboard (
358369 name = leaderboard_name ,
359370 deadline = date_value ,
360371 definition = definition ,
361372 gpu_types = selected_gpus ,
362373 creator_id = interaction .user .id ,
363374 forum_id = forum_id ,
364375 )
376+
377+ # create entry in milestones table.
378+ for milestone in definition .milestones :
379+ db .create_milestone (
380+ lb_id ,
381+ milestone .name ,
382+ milestone .code ,
383+ description = milestone .description ,
384+ )
365385 except KernelBotError as e :
366386 await send_discord_message (
367387 interaction ,
@@ -371,6 +391,122 @@ async def create_leaderboard_in_db(
371391 return False
372392 return True
373393
394+ async def _submit_milestones (
395+ self , interaction : discord .Interaction , leaderboard_name : str , gpus : Optional [list ] = None
396+ ):
397+ backend = self .bot .backend
398+
399+ with self .bot .leaderboard_db as db :
400+ leaderboard_item = db .get_leaderboard (leaderboard_name )
401+ milestones = db .get_leaderboard_milestones (leaderboard_item ["id" ])
402+
403+ task : "LeaderboardTask" = leaderboard_item ["task" ]
404+
405+ # ok, submit all that are missing
406+ submit_tasks = []
407+ from kernelbot .discord_reporter import MultiProgressReporterDiscord
408+
409+ reporters = MultiProgressReporterDiscord (interaction )
410+ await reporters .show (f"Milestone runs for { leaderboard_name } " )
411+
412+ async def submit_milestone (milestone , gpu , reporter ):
413+ result = await backend .submit_leaderboard (
414+ - 1 ,
415+ milestone ["code" ],
416+ "milestone.py" ,
417+ gpu ,
418+ reporter ,
419+ task ,
420+ SubmissionMode .LEADERBOARD ,
421+ None ,
422+ )
423+
424+ # we do not allow milestone runs to fail
425+ if not result .success :
426+ logger .error (f"Milestone run failed: { result } " )
427+ raise KernelBotError (f"Milestone run failed: { result .error } " )
428+
429+ for key , value in result .runs .items ():
430+ if not value .run .success or not value .run .passed :
431+ logger .error (f"Milestone run { key } failed: { value } " )
432+ raise KernelBotError (f"Milestone run { key } failed." )
433+
434+ with backend .db as db :
435+ for key , value in result .runs .items ():
436+ db .create_submission_run (
437+ milestone = milestone ["id" ],
438+ start = value .start ,
439+ end = value .end ,
440+ mode = key ,
441+ runner = gpu .name ,
442+ score = compute_score (result , task , - 1 ),
443+ secret = False ,
444+ compilation = value .compilation ,
445+ result = value .run ,
446+ system = result .system ,
447+ )
448+
449+ if gpus is None :
450+ gpus = leaderboard_item ["gpu_types" ]
451+
452+ for milestone in milestones :
453+ with backend .db as db :
454+ existing_runs = db .get_runs_generic (milestone_id = milestone ["id" ])
455+ # create tasks
456+ for gpu in gpus :
457+ if gpu in [r ["runner" ] for r in existing_runs ]:
458+ await send_discord_message (
459+ interaction ,
460+ f"Skipping { gpu } ; milestone run already exists." ,
461+ ephemeral = True ,
462+ )
463+ continue
464+ submit_tasks .append (
465+ submit_milestone (
466+ milestone ,
467+ get_gpu_by_name (gpu ),
468+ reporters .add_run (f"Milestone { milestone ['name' ]} on { gpu } " ),
469+ )
470+ )
471+
472+ await send_discord_message (
473+ interaction ,
474+ f"Submitted { len (submit_tasks )} milestone runs for { len (milestones )} milestones." ,
475+ ephemeral = True ,
476+ )
477+
478+ # Execute all milestone submissions
479+ await asyncio .gather (* submit_tasks )
480+
481+ @app_commands .describe (
482+ leaderboard_name = "Name of Leaderboard" ,
483+ gpu = "Select GPU. Leave empty to run for all GPUs." ,
484+ rerun = "Force re-running existing milestones." ,
485+ )
486+ @app_commands .autocomplete (leaderboard_name = leaderboard_name_autocomplete )
487+ @with_error_handling
488+ async def trigger_milestones (
489+ self ,
490+ interaction : discord .Interaction ,
491+ leaderboard_name : str ,
492+ gpu : Optional [str ],
493+ rerun : Optional [bool ] = False ,
494+ ):
495+ if not await self .admin_check (interaction ):
496+ await send_discord_message (
497+ interaction , "You do not have permission to trigger milestones." , ephemeral = True
498+ )
499+ return
500+
501+ if rerun :
502+ if gpu is not None :
503+ raise KernelBotError ("Cannot specify `rerun` and `gpu` at the same time" )
504+ with self .bot .backend .db as db :
505+ db .delete_milestone_runs (leaderboard_name )
506+
507+ await interaction .response .defer (ephemeral = True )
508+ await self ._submit_milestones (interaction , leaderboard_name , gpus = gpu )
509+
374510 @discord .app_commands .describe (leaderboard_name = "Name of the leaderboard" )
375511 @discord .app_commands .autocomplete (leaderboard_name = leaderboard_name_autocomplete )
376512 @with_error_handling
0 commit comments