Skip to content

Commit a4cee43

Browse files
Feat: submission flags (#12)
* Feat: submission flags * Fix: flag handling parity between submit and implicit submit * fixes --------- Co-authored-by: Mark Saroufim <[email protected]>
1 parent fe8f3d8 commit a4cee43

File tree

3 files changed

+158
-39
lines changed

3 files changed

+158
-39
lines changed

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,14 @@ Set the `POPCORN_API_URL` environment variable to the URL of the Popcorn API. Yo
3232

3333
## Make your first submission
3434

35-
After this, you can submit a solution by running:
35+
```bash
36+
wget https://raw.githubusercontent.com/gpu-mode/reference-kernels/refs/heads/main/problems/pmpp/grayscale_py/submission.py
37+
popcorn-cli submit --gpu A100 --leaderboard grayscale --mode leaderboard submission.py
38+
```
39+
40+
## Discover new problems
41+
42+
The CLI supports (almost) everything Discord does, so you can also discovery which leaderboards are available. To make discovery more pleasant we also offer a CLI experience.
3643

3744
```bash
3845
popcorn-cli submit <submission-file>

src/cmd/mod.rs

Lines changed: 66 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,18 @@ pub struct Cli {
4343

4444
/// Optional: Path to the solution file
4545
filepath: Option<String>,
46+
47+
/// Optional: Directly specify the GPU to use (e.g., "mi300")
48+
#[arg(long)]
49+
pub gpu: Option<String>,
50+
51+
/// Optional: Directly specify the leaderboard (e.g., "fp8")
52+
#[arg(long)]
53+
pub leaderboard: Option<String>,
54+
55+
/// Optional: Specify submission mode (test, benchmark, leaderboard, profile)
56+
#[arg(long)]
57+
pub mode: Option<String>,
4658
}
4759

4860
#[derive(Subcommand, Debug)]
@@ -62,7 +74,20 @@ enum Commands {
6274
provider: AuthProvider,
6375
},
6476
Submit {
77+
/// Optional: Path to the solution file (can also be provided as a top-level argument)
6578
filepath: Option<String>,
79+
80+
/// Optional: Directly specify the GPU to use (e.g., "MI300")
81+
#[arg(long)]
82+
gpu: Option<String>,
83+
84+
/// Optional: Directly specify the leaderboard (e.g., "amd-fp8-mm")
85+
#[arg(long)]
86+
leaderboard: Option<String>,
87+
88+
/// Optional: Specify submission mode (test, benchmark, leaderboard, profile)
89+
#[arg(long)]
90+
mode: Option<String>,
6691
},
6792
}
6893

@@ -82,7 +107,7 @@ pub async fn execute(cli: Cli) -> Result<()> {
82107
};
83108
auth::run_auth(false, provider_str).await
84109
}
85-
Some(Commands::Submit { filepath }) => {
110+
Some(Commands::Submit { filepath, gpu, leaderboard, mode }) => {
86111
let config = load_config()?;
87112
let cli_id = config.cli_id.ok_or_else(|| {
88113
anyhow!(
@@ -91,19 +116,49 @@ pub async fn execute(cli: Cli) -> Result<()> {
91116
.map_or_else(|_| "unknown path".to_string(), |p| p.display().to_string())
92117
)
93118
})?;
94-
let file_to_submit = filepath.or(cli.filepath);
95-
submit::run_submit_tui(file_to_submit, cli_id).await
119+
// Use filepath from Submit command first, fallback to top-level filepath
120+
let final_filepath = filepath.or(cli.filepath);
121+
submit::run_submit_tui(
122+
final_filepath, // Resolved filepath
123+
gpu, // From Submit command
124+
leaderboard, // From Submit command
125+
mode, // From Submit command
126+
cli_id,
127+
)
128+
.await
96129
}
97130
None => {
98-
let config = load_config()?;
99-
let cli_id = config.cli_id.ok_or_else(|| {
100-
anyhow!(
101-
"cli_id not found in config file ({}). Please run `popcorn register` first.",
102-
get_config_path()
103-
.map_or_else(|_| "unknown path".to_string(), |p| p.display().to_string())
131+
// Check if any of the submission-related flags were used at the top level
132+
if cli.gpu.is_some() || cli.leaderboard.is_some() || cli.mode.is_some() {
133+
return Err(anyhow!(
134+
"Please use the 'submit' subcommand when specifying submission options:\n\
135+
popcorn-cli submit [--gpu GPU] [--leaderboard LEADERBOARD] [--mode MODE] FILEPATH"
136+
));
137+
}
138+
139+
// Handle the case where only a filepath is provided (for backward compatibility)
140+
if let Some(top_level_filepath) = cli.filepath {
141+
let config = load_config()?;
142+
let cli_id = config.cli_id.ok_or_else(|| {
143+
anyhow!(
144+
"cli_id not found in config file ({}). Please run `popcorn register` first.",
145+
get_config_path()
146+
.map_or_else(|_| "unknown path".to_string(), |p| p.display().to_string())
147+
)
148+
})?;
149+
150+
// Run TUI with only filepath, no other options
151+
submit::run_submit_tui(
152+
Some(top_level_filepath),
153+
None, // No GPU option
154+
None, // No leaderboard option
155+
None, // No mode option
156+
cli_id,
104157
)
105-
})?;
106-
submit::run_submit_tui(cli.filepath, cli_id).await
158+
.await
159+
} else {
160+
Err(anyhow!("No command or submission file specified. Use --help for usage."))
161+
}
107162
}
108163
}
109164
}

src/cmd/submit.rs

Lines changed: 84 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -596,7 +596,13 @@ fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
596596
.split(popup_layout[1])[1]
597597
}
598598

599-
pub async fn run_submit_tui(filepath: Option<String>, cli_id: String) -> Result<()> {
599+
pub async fn run_submit_tui(
600+
filepath: Option<String>,
601+
gpu: Option<String>,
602+
leaderboard: Option<String>,
603+
mode: Option<String>,
604+
cli_id: String,
605+
) -> Result<()> {
600606
let file_to_submit = match filepath {
601607
Some(fp) => fp,
602608
None => {
@@ -620,40 +626,91 @@ pub async fn run_submit_tui(filepath: Option<String>, cli_id: String) -> Result<
620626
));
621627
}
622628

629+
// Perform direct submission if all required parameters are provided via CLI
630+
if let (Some(gpu_flag), Some(leaderboard_flag), Some(mode_flag)) = (&gpu, &leaderboard, &mode) {
631+
// Read file content
632+
let mut file = File::open(&file_to_submit)?;
633+
let mut file_content = String::new();
634+
file.read_to_string(&mut file_content)?;
635+
636+
// Create client and submit directly
637+
let client = service::create_client(Some(cli_id))?;
638+
println!("Submitting solution directly with:");
639+
println!(" File: {}", file_to_submit);
640+
println!(" Leaderboard: {}", leaderboard_flag);
641+
println!(" GPU: {}", gpu_flag);
642+
println!(" Mode: {}", mode_flag);
643+
644+
// Make the submission
645+
let result = service::submit_solution(
646+
&client,
647+
&file_to_submit,
648+
&file_content,
649+
leaderboard_flag,
650+
gpu_flag,
651+
mode_flag
652+
).await?;
653+
654+
utils::display_ascii_art();
655+
println!("{}", result);
656+
return Ok(());
657+
}
658+
623659
let mut app = App::new(&file_to_submit, cli_id);
624-
app.initialize_with_directives(directives);
625660

661+
// Override directives with CLI flags if provided
662+
if let Some(gpu_flag) = gpu {
663+
app.selected_gpu = Some(gpu_flag);
664+
}
665+
if let Some(leaderboard_flag) = leaderboard {
666+
app.selected_leaderboard = Some(leaderboard_flag);
667+
}
668+
if let Some(mode_flag) = mode {
669+
app.selected_submission_mode = Some(mode_flag);
670+
// Skip to submission if we have all required fields
671+
if app.selected_gpu.is_some() && app.selected_leaderboard.is_some() {
672+
app.modal_state = ModelState::WaitingForResult;
673+
}
674+
}
675+
676+
// If no CLI flags, use directives
677+
if app.selected_gpu.is_none() && app.selected_leaderboard.is_none() {
678+
app.initialize_with_directives(directives);
679+
}
680+
681+
// Spawn the initial task based on the starting state BEFORE setting up the TUI
682+
// If spawning fails here, we just return the error directly without TUI cleanup.
683+
match app.modal_state {
684+
ModelState::LeaderboardSelection => {
685+
if let Err(e) = app.spawn_load_leaderboards() {
686+
return Err(anyhow!("Error starting leaderboard fetch: {}", e));
687+
}
688+
}
689+
ModelState::GpuSelection => {
690+
if let Err(e) = app.spawn_load_gpus() {
691+
return Err(anyhow!("Error starting GPU fetch: {}", e));
692+
}
693+
}
694+
ModelState::WaitingForResult => {
695+
// This state occurs when all flags (gpu, leaderboard, mode) are provided
696+
if let Err(e) = app.spawn_submit_solution() {
697+
return Err(anyhow!("Error starting submission: {}", e));
698+
}
699+
}
700+
_ => {
701+
// Other states like SubmissionModeSelection shouldn't be the *initial* state
702+
// unless there's a logic error elsewhere. We'll proceed to TUI.
703+
}
704+
}
705+
706+
// Now, set up the TUI
626707
enable_raw_mode()?;
627708
let mut stdout = io::stdout();
628709
crossterm::execute!(stdout, EnterAlternateScreen)?;
629710
let backend = CrosstermBackend::new(stdout);
630711
let mut terminal = Terminal::new(backend)?;
631712

632-
if app.modal_state == ModelState::LeaderboardSelection {
633-
if let Err(e) = app.spawn_load_leaderboards() {
634-
// Cleanup terminal before exiting on initial load error
635-
disable_raw_mode()?;
636-
crossterm::execute!(
637-
terminal.backend_mut(),
638-
crossterm::terminal::LeaveAlternateScreen
639-
)?;
640-
terminal.show_cursor()?;
641-
return Err(anyhow!("Error starting leaderboard fetch: {}", e));
642-
}
643-
} else if app.modal_state == ModelState::GpuSelection {
644-
if let Err(e) = app.spawn_load_gpus() {
645-
// Cleanup terminal before exiting on initial load error
646-
disable_raw_mode()?;
647-
crossterm::execute!(
648-
terminal.backend_mut(),
649-
crossterm::terminal::LeaveAlternateScreen
650-
)?;
651-
terminal.show_cursor()?;
652-
return Err(anyhow!("Error starting GPU fetch: {}", e));
653-
}
654-
}
655-
656-
// Main application loop
713+
// Main application loop - this remains largely the same
657714
while !app.should_quit {
658715
terminal.draw(|f| ui(&app, f))?;
659716

0 commit comments

Comments
 (0)