Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 0 additions & 31 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,43 +7,12 @@ import (
"github.com/S1ro1/popcorn-cli/src/cmd"
)

func displayAsciiArt() {
art := `
_ __ _ ______ _
| | / / | | | ___ \ | |
| |/ / ___ _ __ _ __ ___ | | | |_/ / ___ _| |_
| \ / _ \ '__| '_ \ / _ \| | | ___ \ / _ \| | __|
| |\ \ __/ | | | | | __/| | | |_/ /| (_) | | |_
\_| \_/\___|_| |_| |_|\___|_/ \____/ \___/|_|\__|

POPCORN CLI - GPU MODE

┌───────────────────────────────────────┐
│ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │ooOoo│ │ooOoo│ │ooOoo│ │▒
│ │oOOOo│ │oOOOo│ │oOOOo│ │▒
│ │ooOoo│ │ooOoo│ │ooOoo│ ┌────────┐ │▒
│ └─────┘ └─────┘ └─────┘ │████████│ │▒
│ │████████│ │▒
│ ┌────────────────────────┐ │████████│ │▒
│ │ │ │████████│ │▒
│ │ POPCORN GPU COMPUTE │ └────────┘ │▒
│ │ │ │▒
│ └────────────────────────┘ │▒
│ │▒
└───────────────────────────────────────┘▒
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
`
fmt.Println(art)
}

func main() {

if os.Getenv("POPCORN_API_URL") == "" {
fmt.Println("POPCORN_API_URL is not set. Please set it to the URL of the Popcorn API.")
os.Exit(1)
}
displayAsciiArt()
cmd.Execute()
}
114 changes: 72 additions & 42 deletions src/cmd/popcorn-cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cmd
import (
"fmt"
"os"
"strings"

"github.com/charmbracelet/bubbles/list"
"github.com/charmbracelet/bubbles/spinner"
Expand All @@ -11,14 +12,11 @@ import (
"github.com/S1ro1/popcorn-cli/src/models"
"github.com/S1ro1/popcorn-cli/src/service"

"github.com/S1ro1/popcorn-cli/src/utils"

tea "github.com/charmbracelet/bubbletea"
)

var runnerItems = []list.Item{
models.RunnerItem{TitleText: "Modal", DescriptionText: "Submit a solution to be evaluated on Modal runners.", Value: "modal"},
models.RunnerItem{TitleText: "Github", DescriptionText: "Submit a solution to be evaluated on Github runners. This can take a little longer to spin up.", Value: "github"},
}

var submissionModeItems = []list.Item{
models.SubmissionModeItem{TitleText: "Test", DescriptionText: "Test the solution and give detailed results about passed/failed tests.", Value: "test"},
models.SubmissionModeItem{TitleText: "Benchmark", DescriptionText: "Benchmark the solution, this also runs the tests and afterwards runs the benchmark, returning detailed timing results", Value: "benchmark"},
Expand All @@ -35,8 +33,6 @@ type model struct {
filepath string
leaderboardsList list.Model
selectedLeaderboard string
runnersList list.Model
selectedRunner string
gpusList list.Model
selectedGpu string
submissionModeList list.Model
Expand All @@ -58,6 +54,17 @@ func (m model) Init() tea.Cmd {
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd

if len(m.gpusList.Items()) == 0 && m.modalState == models.ModelStateGpuSelection {
gpus, err := service.GetListItems(func() ([]models.GpuItem, error) {
return service.FetchAvailableGpus(m.selectedLeaderboard)
})
if err != nil {
m.SetError(fmt.Sprintf("Error fetching GPUs: %s", err))
return m, tea.Quit
}
m.gpusList = list.New(gpus, list.NewDefaultDelegate(), m.width-2, m.height-2)
m.gpusList.SetSize(m.width-2, m.height-2)
}
if !m.finishedOkay {
return m, tea.Quit
}
Expand All @@ -72,25 +79,26 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case models.ModelStateLeaderboardSelection:
if i := m.leaderboardsList.SelectedItem(); i != nil {
m.selectedLeaderboard = i.(models.LeaderboardItem).TitleText
m.modalState = models.ModelStateRunnerSelection
m.runnersList.SetSize(m.width-2, m.height-2)
}
case models.ModelStateRunnerSelection:
if i := m.runnersList.SelectedItem(); i != nil {
m.selectedRunner = i.(models.RunnerItem).Value
m.modalState = models.ModelStateGpuSelection
gpus, err := service.GetListItems(func() ([]models.GpuItem, error) {
return service.FetchAvailableGpus(m.selectedLeaderboard, m.selectedRunner)
})
if err != nil {
m.SetError(fmt.Sprintf("Error fetching GPUs: %s", err))
return m, tea.Quit
// No gpu selected in popcorn directives, fetch gpus and move to gpu selection
if m.selectedGpu == "" {
gpus, err := service.GetListItems(func() ([]models.GpuItem, error) {
return service.FetchAvailableGpus(m.selectedLeaderboard)
})
if err != nil {
m.SetError(fmt.Sprintf("Error fetching GPUs: %s", err))
return m, tea.Quit
}
if len(gpus) == 0 {
m.SetError("No GPUs available for this leaderboard.")
return m, tea.Quit
}
m.gpusList = list.New(gpus, list.NewDefaultDelegate(), m.width-2, m.height-2)
m.gpusList.SetSize(m.width-2, m.height-2)
m.modalState = models.ModelStateGpuSelection
} else {
m.modalState = models.ModelStateSubmissionModeSelection
m.submissionModeList.SetSize(m.width-2, m.height-2)
}
if len(gpus) == 0 {
m.SetError("No GPUs available for this runner and leaderboard.")
return m, tea.Quit
}
m.gpusList = list.New(gpus, list.NewDefaultDelegate(), m.width-2, m.height-2)
}
case models.ModelStateGpuSelection:
if i := m.gpusList.SelectedItem(); i != nil {
Expand Down Expand Up @@ -119,8 +127,6 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch m.modalState {
case models.ModelStateLeaderboardSelection:
m.leaderboardsList.SetSize(listWidth, listHeight)
case models.ModelStateRunnerSelection:
m.runnersList.SetSize(listWidth, listHeight)
case models.ModelStateGpuSelection:
m.gpusList.SetSize(listWidth, listHeight)
case models.ModelStateSubmissionModeSelection:
Expand All @@ -131,8 +137,6 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch m.modalState {
case models.ModelStateLeaderboardSelection:
m.leaderboardsList, cmd = m.leaderboardsList.Update(msg)
case models.ModelStateRunnerSelection:
m.runnersList, cmd = m.runnersList.Update(msg)
case models.ModelStateGpuSelection:
m.gpusList, cmd = m.gpusList.Update(msg)
case models.ModelStateSubmissionModeSelection:
Expand All @@ -159,8 +163,6 @@ func (m model) View() string {
switch m.modalState {
case models.ModelStateLeaderboardSelection:
content = m.leaderboardsList.View()
case models.ModelStateRunnerSelection:
content = m.runnersList.View()
case models.ModelStateGpuSelection:
content = m.gpusList.View()
case models.ModelStateSubmissionModeSelection:
Expand All @@ -182,12 +184,14 @@ func (m model) Submit() tea.Cmd {
go func() {
fileContent, err := os.ReadFile(m.filepath)
if err != nil {
p.Send(models.ErrorMsg{Err: fmt.Errorf("error reading file: %s", err)})
m.SetError(fmt.Sprintf("Error reading file: %s", err))
return
}

prettyResult, err := service.SubmitSolution(m.selectedLeaderboard, m.selectedRunner, m.selectedGpu, m.selectedSubmissionMode, m.filepath, fileContent)
prettyResult, err := service.SubmitSolution(m.selectedLeaderboard, m.selectedGpu, m.selectedSubmissionMode, m.filepath, fileContent)
if err != nil {
p.Send(models.ErrorMsg{Err: fmt.Errorf("error submitting solution: %s", err)})
m.SetError(fmt.Sprintf("Error submitting solution: %s", err))
return
}
Expand All @@ -213,28 +217,53 @@ func Execute() {
return
}

popcornDirectives, err := utils.GetPopcornDirectives(filepath)
if err != nil {
fmt.Println("Error:", err)
var input string
fmt.Scanln(&input)
if strings.ToLower(input) != "y" {
return
}
}

var modalState models.ModelState
if popcornDirectives.LeaderboardName != "" && len(popcornDirectives.Gpus) > 0 {
modalState = models.ModelStateSubmissionModeSelection
} else if popcornDirectives.LeaderboardName != "" {
modalState = models.ModelStateGpuSelection
} else {
modalState = models.ModelStateLeaderboardSelection
}

var selectedGpu string
if len(popcornDirectives.Gpus) > 0 {
selectedGpu = popcornDirectives.Gpus[0]
}

leaderboardItems, err := service.GetListItems(service.FetchLeaderboards)
if err != nil {
fmt.Println("Error fetching leaderboards:", err)
return

}

s := spinner.New()
s.Spinner = spinner.Dot
s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("205"))

m := model{
filepath: filepath,
leaderboardsList: list.New(leaderboardItems, list.NewDefaultDelegate(), 0, 0),
runnersList: list.New(runnerItems, list.NewDefaultDelegate(), 0, 0),
submissionModeList: list.New(submissionModeItems, list.NewDefaultDelegate(), 0, 0),
spinner: s,
modalState: models.ModelStateLeaderboardSelection,
finishedOkay: true,
finalStatus: "",
filepath: filepath,
leaderboardsList: list.New(leaderboardItems, list.NewDefaultDelegate(), 0, 0),
submissionModeList: list.New(submissionModeItems, list.NewDefaultDelegate(), 0, 0),
gpusList: list.New([]list.Item{}, list.NewDefaultDelegate(), 0, 0),
spinner: s,
modalState: modalState,
finishedOkay: true,
finalStatus: "",
selectedLeaderboard: popcornDirectives.LeaderboardName,
selectedGpu: selectedGpu,
}
m.leaderboardsList.Title = "Leaderboards"
m.runnersList.Title = "Runners"

p = tea.NewProgram(m)
finalModel, err := p.Run()
Expand All @@ -244,6 +273,7 @@ func Execute() {
}

m, ok := finalModel.(model)
utils.DisplayAsciiArt()
if ok && m.finishedOkay {
fmt.Printf("\nResult:\n\n%s\n", m.finalStatus)
} else if ok && !m.finishedOkay {
Expand Down
2 changes: 1 addition & 1 deletion src/models/types.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package models

type LeaderboardItem struct {
TitleText string
TitleText string
TaskDescription string
}

Expand Down
15 changes: 6 additions & 9 deletions src/service/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,21 +41,20 @@ func FetchLeaderboards() ([]models.LeaderboardItem, error) {
return nil, err
}


leaderboardNames := make([]models.LeaderboardItem, len(leaderboards))
for i, lb := range leaderboards {
task := lb["task"].(map[string]interface{})
leaderboardNames[i] = models.LeaderboardItem{
TitleText: lb["name"].(string),
TaskDescription: task["description"].(string),
TitleText: lb["name"].(string),
TaskDescription: task["description"].(string),
}
}

return leaderboardNames, nil
}

func FetchAvailableGpus(leaderboard string, runner string) ([]models.GpuItem, error) {
resp, err := http.Get(BASE_URL + "/" + leaderboard + "/" + runner + "/gpus")
func FetchAvailableGpus(leaderboard string) ([]models.GpuItem, error) {
resp, err := http.Get(BASE_URL + "/gpus/" + leaderboard)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -84,7 +83,7 @@ func FetchAvailableGpus(leaderboard string, runner string) ([]models.GpuItem, er
return gpuItems, nil
}

func SubmitSolution(leaderboard string, runner string, gpu string, submissionMode string, filename string, fileContent []byte) (string, error) {
func SubmitSolution(leaderboard string, gpu string, submissionMode string, filename string, fileContent []byte) (string, error) {
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)

Expand All @@ -101,10 +100,9 @@ func SubmitSolution(leaderboard string, runner string, gpu string, submissionMod
return "", fmt.Errorf("error closing form: %s", err)
}

url := fmt.Sprintf("%s/%s/%s/%s/%s",
url := fmt.Sprintf("%s/%s/%s/%s",
BASE_URL,
strings.ToLower(leaderboard),
strings.ToLower(runner),
strings.ToLower(gpu),
strings.ToLower(submissionMode))

Expand Down Expand Up @@ -161,4 +159,3 @@ func GetListItems[T list.Item](fetchFn func() ([]T, error)) ([]list.Item, error)

return listItems, nil
}

82 changes: 82 additions & 0 deletions src/utils/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package utils

import (
"fmt"
"os"
"strings"
)

type PopcornDirectives struct {
LeaderboardName string
Gpus []string
}

func GetPopcornDirectives(filepath string) (*PopcornDirectives, error) {
var err error = nil
content, err := os.ReadFile(filepath)

var gpus []string = []string{}
var leaderboard_name string = ""

if err != nil {
return nil, err
}

lines := strings.Split(string(content), "\n")
for _, line := range lines {
if !strings.HasPrefix(line, "//") && !strings.HasPrefix(line, "#") {
continue
}

parts := strings.Split(line, " ")
if parts[0] == "//!POPCORN" || parts[0] == "#!POPCORN" {
arg := strings.ToLower(parts[1])
if arg == "gpu" || arg == "gpus" {
gpus = parts[2:]
} else if arg == "leaderboard" {
leaderboard_name = parts[2]
}
}
}

if len(gpus) > 1 {
err = fmt.Errorf("multiple GPUs are not yet supported, continue with the first gpu? (%s) [y/N]", gpus[0])
gpus = []string{gpus[0]}
}

return &PopcornDirectives{
LeaderboardName: leaderboard_name,
Gpus: gpus,
}, err
}

func DisplayAsciiArt() {
art := `
_ __ _ ______ _
| | / / | | | ___ \ | |
| |/ / ___ _ __ _ __ ___ | | | |_/ / ___ _| |_
| \ / _ \ '__| '_ \ / _ \| | | ___ \ / _ \| | __|
| |\ \ __/ | | | | | __/| | | |_/ /| (_) | | |_
\_| \_/\___|_| |_| |_|\___|_/ \____/ \___/|_|\__|

POPCORN CLI - GPU MODE

┌───────────────────────────────────────┐
│ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │ooOoo│ │ooOoo│ │ooOoo│ │▒
│ │oOOOo│ │oOOOo│ │oOOOo│ │▒
│ │ooOoo│ │ooOoo│ │ooOoo│ ┌────────┐ │▒
│ └─────┘ └─────┘ └─────┘ │████████│ │▒
│ │████████│ │▒
│ ┌────────────────────────┐ │████████│ │▒
│ │ │ │████████│ │▒
│ │ POPCORN GPU COMPUTE │ └────────┘ │▒
│ │ │ │▒
│ └────────────────────────┘ │▒
│ │▒
└───────────────────────────────────────┘▒
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
`
fmt.Println(art)
}
Loading