diff --git a/README.md b/README.md index 732c77f..4afa2b3 100644 --- a/README.md +++ b/README.md @@ -7,16 +7,16 @@ ## Easily deploy GPU models as serverless APIs -- Deploy a template from the registry +- Deploy a template ```bash -kuda deploy -f cyrildiagne/nvidiasmi-http +$ kuda deploy -f https://raw.githubusercontent.com/cyrildiagne/kuda/releases/v0.4.0/example-hello-gpu-flask.yaml ``` - Call it! ```bash -$ curl -H 'x-api-key: $your_key' https://nvidiasmi.$your_namespace.example.com +$ curl -H 'x-api-key: $your_key' https://nvidiasmi.default.$your_domain ``` ``` @@ -28,8 +28,8 @@ Hello GPU! | GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC | | Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. | |===============================+======================+======================| -| 0 Tesla K80 Off | 00000000:00:04.0 Off | 0 | -| N/A 37C P8 27W / 149W | 0MiB / 11441MiB | 0% Default | +| 0 Tesla T4 Off | 00000000:00:04.0 Off | 0 | +| N/A 37C P8 10W / 70W | 0MiB / 15079MiB | 0% Default | +-------------------------------+----------------------+----------------------+ +-----------------------------------------------------------------------------+ @@ -70,11 +70,14 @@ Checkout the full list of templates available in [the registry](#). ## Serverless GPU inference -Kuda builds on [Knative](#) to allocate cloud GPUs only when there is traffic to your app. +Kuda builds on [Knative](#) to allocate cloud GPUs only when there is traffic +to your app. -This is ideal when you want to share ML projects online without keeping expensive GPUs allocated all the time. +This is ideal when you want to share ML projects online without keeping +expensive GPUs allocated all the time. -It tries to reduce cold starts time (gpu nodes allocation and service instanciation) as much possible and to tries manage cooldown times intelligently. +It tries to reduce cold starts time (gpu nodes allocation and service instanciation) +as much possible and to tries manage cooldown times intelligently. ## Turn any model into a serverless API diff --git a/cmd/cli/generate.go b/cmd/cli/generate.go new file mode 100644 index 0000000..3235190 --- /dev/null +++ b/cmd/cli/generate.go @@ -0,0 +1,96 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/cyrildiagne/kuda/pkg/api" + "github.com/cyrildiagne/kuda/pkg/config" + "github.com/cyrildiagne/kuda/pkg/utils" + "github.com/spf13/cobra" + yaml "gopkg.in/yaml.v2" + + skaffoldv1 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1" +) + +// generateCmd represents the `kuda deploy` command. +var generateCmd = &cobra.Command{ + Use: "generate", + Short: "Generate the deployment files locally.", + Run: func(cmd *cobra.Command, args []string) { + registry, err := cmd.Flags().GetString("registry") + if err != nil { + panic(err) + } + + folder, _ := cmd.Flags().GetString("to") + if err := generate(folder, registry); err != nil { + panic(err) + } + }, +} + +func init() { + RootCmd.AddCommand(generateCmd) + generateCmd.Flags().StringP("registry", "r", "", "Registry where the image is pushed (Eg: gcr.io/kuda-project).") + generateCmd.MarkFlagRequired("registry") + generateCmd.Flags().StringP("to", "t", ".kuda", "Destination folder (default: .kuda)") +} + +func generate(folder string, registry string) error { + // Load the manifest. + manifestFile := "./kuda.yaml" + manifest, err := utils.LoadManifest(manifestFile) + if err != nil { + fmt.Println("Could not load manifest", manifestFile) + return err + } + + // Make sure output folder exists. + if _, err := os.Stat(folder); os.IsNotExist(err) { + os.Mkdir(folder, 0700) + } + + // Generate the files. + im := api.ImageName{ + Author: cfg.Namespace, + Name: manifest.Name, + } + + buildType := &skaffoldv1.BuildType{ + LocalBuild: &skaffoldv1.LocalBuild{}, + } + + // Generate Skaffold & Knative config files. + service := config.ServiceSummary{ + Name: manifest.Name, + Namespace: cfg.Namespace, + DockerArtifact: registry + "/" + im.GetID(), + BuildType: buildType, + } + + // Add a release version of the manifest. + manifest.Release = service.DockerArtifact + manifYAML, err := yaml.Marshal(manifest) + if err != nil { + return err + } + manifYAML = []byte("# Generated automatically\n" + string(manifYAML)) + manifFile := filepath.FromSlash(folder + "/kuda.yaml") + if err := utils.WriteYAML(manifYAML, manifFile); err != nil { + return err + } + + // Export API version in an env var for Skaffold's tagger. + if err := utils.GenerateSkaffoldConfigFiles(service, manifest.Dev, folder+"/dev"); err != nil { + return err + } + if err := utils.GenerateSkaffoldConfigFiles(service, manifest.Deploy, folder+"/deploy"); err != nil { + return err + } + + fmt.Printf("Files generated in %s\n", folder) + + return nil +} diff --git a/pkg/manifest/latest/manifest.go b/pkg/manifest/latest/manifest.go index d3ce7c1..aae55f9 100644 --- a/pkg/manifest/latest/manifest.go +++ b/pkg/manifest/latest/manifest.go @@ -10,18 +10,25 @@ type Manifest struct { ManivestVersion string `yaml:"kudaManifestVersion"` Version string `yaml:"version,omitempty"` Name string `yaml:"name"` - Meta Meta `yaml:"meta,omitempty"` - Deploy Config `yaml:"deploy"` - Dev Config `yaml:"dev,omitempty"` + License string `yaml:"license,omitempty"` + // Meta Meta `yaml:"meta,omitempty"` + + // The dev & deploy configs. + Deploy Config `yaml:"deploy"` + Dev Config `yaml:"dev,omitempty"` + // Paths *openapi.Paths `yaml:"paths,omitempty"` + + // Release can contain the path to the deployed container. + Release string `yaml:"release,omitempty"` } // Meta stores the metadata. -type Meta struct { - Author string `yaml:"author,omitempty"` - Repository string `yaml:"repository,omitempty"` - License string `yaml:"license,omitempty"` -} +// type Meta struct { +// Author string `yaml:"author,omitempty"` +// Repository string `yaml:"repository,omitempty"` +// License string `yaml:"license,omitempty"` +// } // Config stores a deployment config. type Config struct {