diff --git a/cmd/cog/cog.go b/cmd/cog/cog.go index a0ae6bb1b2..3d530f1f3c 100644 --- a/cmd/cog/cog.go +++ b/cmd/cog/cog.go @@ -9,10 +9,10 @@ import ( func main() { cmd, err := cli.NewRootCommand() if err != nil { - console.Fatal("%f", err) + console.Fatalf("%f", err) } if err = cmd.Execute(); err != nil { - console.Fatal("%s", err) + console.Fatalf("%s", err) } } diff --git a/cmd/generate_compatibility_matrices/main.go b/cmd/generate_compatibility_matrices/main.go index 12ddea26e2..07031d1c48 100644 --- a/cmd/generate_compatibility_matrices/main.go +++ b/cmd/generate_compatibility_matrices/main.go @@ -28,23 +28,23 @@ func main() { if *tfOutputPath != "" { if err := writeTFCompatibilityMatrix(*tfOutputPath); err != nil { - console.Fatal("Failed to write Tensorflow compatibility matrix: %s", err) + console.Fatalf("Failed to write Tensorflow compatibility matrix: %s", err) } } if *torchOutputPath != "" { if err := writeTorchCompatibilityMatrix(*torchOutputPath); err != nil { - console.Fatal("Failed to write PyTorch compatibility matrix: %s", err) + console.Fatalf("Failed to write PyTorch compatibility matrix: %s", err) } } if *cudaImagesOutputPath != "" { if err := writeCUDABaseImageTags(*cudaImagesOutputPath); err != nil { - console.Fatal("Failed to write CUDA base images: %s", err) + console.Fatalf("Failed to write CUDA base images: %s", err) } } } func writeTFCompatibilityMatrix(outputPath string) error { - console.Info("Writing Tensorflow compatibility matrix to %s...", outputPath) + console.Infof("Writing Tensorflow compatibility matrix to %s...", outputPath) url := "https://www.tensorflow.org/install/source" resp, err := soup.Get(url) @@ -94,7 +94,7 @@ func writeTFCompatibilityMatrix(outputPath string) error { } func writeTorchCompatibilityMatrix(outputPath string) error { - console.Info("Writing PyTorch compatibility matrix to %s...", outputPath) + console.Infof("Writing PyTorch compatibility matrix to %s...", outputPath) compats := []model.TorchCompatibility{} var err error @@ -123,7 +123,7 @@ func writeTorchCompatibilityMatrix(outputPath string) error { } func writeCUDABaseImageTags(outputPath string) error { - console.Info("Writing CUDA base images to %s...", outputPath) + console.Infof("Writing CUDA base images to %s...", outputPath) url := "https://hub.docker.com/v2/repositories/nvidia/cuda/tags/?page_size=1000&name=devel-ubuntu&ordering=last_updated" resp, err := soup.Get(url) if err != nil { diff --git a/pkg/cli/benchmark.go b/pkg/cli/benchmark.go index 05ec84d9a5..5005378650 100644 --- a/pkg/cli/benchmark.go +++ b/pkg/cli/benchmark.go @@ -18,6 +18,7 @@ import ( var benchmarkSetups int var benchmarkRuns int +var benchmarkTarget string type BenchmarkResults struct { SetupTimes []float64 @@ -35,6 +36,7 @@ func newBenchmarkCommand() *cobra.Command { cmd.Flags().IntVarP(&benchmarkSetups, "setup-iterations", "s", 3, "Number of setup iterations") cmd.Flags().IntVarP(&benchmarkRuns, "run-iterations", "r", 3, "Number of run iterations per setup iteration") + cmd.Flags().StringVarP(&benchmarkTarget, "target", "t", "docker-cpu", "Target to benchmark (e.g. docker-cpu, docker-gpu)") return cmd } @@ -47,7 +49,7 @@ func benchmarkModel(cmd *cobra.Command, args []string) error { id := args[0] cli := client.NewClient() - console.Info("Starting benchmark of %s:%s", repo, id) + console.Infof("Starting benchmark of %s:%s", repo, id) mod, err := cli.GetModel(repo, id) if err != nil { @@ -67,7 +69,7 @@ func benchmarkModel(cmd *cobra.Command, args []string) error { } results := new(BenchmarkResults) for i := 0; i < benchmarkSetups; i++ { - console.Info("Running setup iteration %d", i+1) + console.Infof("Running setup iteration %d", i+1) if err := runBenchmarkInference(mod, modelDir, results, benchmarkRuns); err != nil { return err } @@ -100,13 +102,13 @@ func runBenchmarkInference(mod *model.Model, modelDir string, results *Benchmark logWriter := logger.NewConsoleLogger() bootStart := time.Now() - deployment, err := servingPlatform.Deploy(mod, model.TargetDockerCPU, logWriter) + deployment, err := servingPlatform.Deploy(mod, benchmarkTarget, logWriter) if err != nil { return err } defer func() { if err := deployment.Undeploy(); err != nil { - console.Warn("Failed to kill Docker container: %s", err) + console.Warnf("Failed to kill Docker container: %s", err) } }() bootTime := time.Since(bootStart).Seconds() diff --git a/pkg/cli/build.go b/pkg/cli/build.go index 3a4a4d467a..89e31fe643 100644 --- a/pkg/cli/build.go +++ b/pkg/cli/build.go @@ -40,7 +40,7 @@ func buildModel(cmd *cobra.Command, args []string) error { return fmt.Errorf("%s does not exist in %s. Are you in the right directory?", global.ConfigFilename, projectDir) } - console.Info("Uploading %s to %s", projectDir, repo) + console.Infof("Uploading %s to %s", projectDir, repo) cli := client.NewClient() mod, err := cli.UploadModel(repo, projectDir) diff --git a/pkg/cli/debug.go b/pkg/cli/debug.go index ee1cdc79a9..79f96b1619 100644 --- a/pkg/cli/debug.go +++ b/pkg/cli/debug.go @@ -26,6 +26,7 @@ func newDebugCommand() *cobra.Command { Short: "Generate a Dockerfile from " + global.ConfigFilename, Hidden: true, } + cmd.Flags().StringP("arch", "a", "cpu", "Architecture (cpu/gpu)") cmd.AddCommand(debug) @@ -57,8 +58,15 @@ func cmdDockerfile(cmd *cobra.Command, args []string) error { if err != nil { return err } + if err := config.ValidateAndCompleteConfig(); err != nil { + return err + } - generator := &docker.DockerfileGenerator{Config: config, Arch: "cpu"} + arch, err := cmd.Flags().GetString("arch") + if err != nil { + return err + } + generator := &docker.DockerfileGenerator{Config: config, Arch: arch} out, err := generator.Generate() if err != nil { return err diff --git a/pkg/cli/infer.go b/pkg/cli/infer.go index afcc458a2c..68a7aff188 100644 --- a/pkg/cli/infer.go +++ b/pkg/cli/infer.go @@ -69,7 +69,7 @@ func cmdInfer(cmd *cobra.Command, args []string) error { } defer func() { if err := deployment.Undeploy(); err != nil { - console.Warn("Failed to kill Docker container: %s", err) + console.Warnf("Failed to kill Docker container: %s", err) } }() diff --git a/pkg/cli/repo.go b/pkg/cli/repo.go index baac132a94..8559e60521 100644 --- a/pkg/cli/repo.go +++ b/pkg/cli/repo.go @@ -66,7 +66,7 @@ func setRepo(cmd *cobra.Command, args []string) error { } exists, err := files.FileExists(filepath.Join(cwd, global.ConfigFilename)) if !exists { - console.Warn("%s does not exist in %s. Are you in the right directory?", global.ConfigFilename, cwd) + console.Warnf("%s does not exist in %s. Are you in the right directory?", global.ConfigFilename, cwd) } cli := client.NewClient() diff --git a/pkg/cli/server.go b/pkg/cli/server.go index 4e09b95005..00ee4adb17 100644 --- a/pkg/cli/server.go +++ b/pkg/cli/server.go @@ -48,7 +48,7 @@ func startServer(cmd *cobra.Command, args []string) error { } } - console.Debug("Preparing to start server on port %d", port) + console.Debugf("Preparing to start server on port %d", port) // TODO(andreas): make this configurable dataDir := ".cog" diff --git a/pkg/cli/show.go b/pkg/cli/show.go index 0742bcd82a..f80051e3e1 100644 --- a/pkg/cli/show.go +++ b/pkg/cli/show.go @@ -55,6 +55,12 @@ func showModel(cmd *cobra.Command, args []string) error { if arg.Help != nil { help = *arg.Help } + if arg.Min != nil { + typeStr += ", min: " + *arg.Min + } + if arg.Max != nil { + typeStr += ", max: " + *arg.Max + } if arg.Default != nil { typeStr += ", default: " + *arg.Default } diff --git a/pkg/client/upload.go b/pkg/client/upload.go index 3b36d9f7ac..a049490051 100644 --- a/pkg/client/upload.go +++ b/pkg/client/upload.go @@ -81,7 +81,7 @@ func (c *Client) UploadModel(repo *model.Repo, projectDir string) (*model.Model, msg := new(logger.Message) if err := json.Unmarshal(line, msg); err != nil { console.Debug(string(line)) - console.Warn("Failed to parse console message: %s", err) + console.Warnf("Failed to parse console message: %s", err) continue } switch msg.Type { diff --git a/pkg/console/console.go b/pkg/console/console.go index 50b4696095..be409f8c58 100644 --- a/pkg/console/console.go +++ b/pkg/console/console.go @@ -23,28 +23,54 @@ type Console struct { } // Debug level message -func (c *Console) Debug(msg string, v ...interface{}) { - c.log(DebugLevel, msg, v...) +func (c *Console) Debug(msg string) { + c.log(DebugLevel, msg) } // Info level message -func (c *Console) Info(msg string, v ...interface{}) { - c.log(InfoLevel, msg, v...) +func (c *Console) Info(msg string) { + c.log(InfoLevel, msg) } // Warn level message -func (c *Console) Warn(msg string, v ...interface{}) { - c.log(WarnLevel, msg, v...) +func (c *Console) Warn(msg string) { + c.log(WarnLevel, msg) } // Error level message -func (c *Console) Error(msg string, v ...interface{}) { - c.log(ErrorLevel, msg, v...) +func (c *Console) Error(msg string) { + c.log(ErrorLevel, msg) } // Fatal level message, followed by exit -func (c *Console) Fatal(msg string, v ...interface{}) { - c.log(FatalLevel, msg, v...) +func (c *Console) Fatal(msg string) { + c.log(FatalLevel, msg) + os.Exit(1) +} + +// Debug level message +func (c *Console) Debugf(msg string, v ...interface{}) { + c.log(DebugLevel, fmt.Sprintf(msg, v...)) +} + +// Info level message +func (c *Console) Infof(msg string, v ...interface{}) { + c.log(InfoLevel, fmt.Sprintf(msg, v...)) +} + +// Warn level message +func (c *Console) Warnf(msg string, v ...interface{}) { + c.log(WarnLevel, fmt.Sprintf(msg, v...)) +} + +// Error level message +func (c *Console) Errorf(msg string, v ...interface{}) { + c.log(ErrorLevel, fmt.Sprintf(msg, v...)) +} + +// Fatal level message, followed by exit +func (c *Console) Fatalf(msg string, v ...interface{}) { + c.log(FatalLevel, fmt.Sprintf(msg, v...)) os.Exit(1) } @@ -75,7 +101,7 @@ func (c *Console) DebugOutput(line string) { fmt.Fprintln(os.Stderr, line) } -func (c *Console) log(level Level, msg string, v ...interface{}) { +func (c *Console) log(level Level, msg string) { if level < c.Level { return } @@ -83,12 +109,12 @@ func (c *Console) log(level Level, msg string, v ...interface{}) { prompt := "═══╡ " continuationPrompt := " │ " - formattedMsg := fmt.Sprintf(msg, v...) + formattedMsg := msg // Word wrap width, err := GetWidth() if err != nil { - Debug("error getting width of terminal: %s", err) + Debugf("error getting width of terminal: %s", err) } else if width > 30 { // Only do word wrapping for terminals 30 chars or wider. Anything smaller and the terminal // is probably resized really small for some reason, and when the user resizes it to be big diff --git a/pkg/console/global.go b/pkg/console/global.go index 5c3bb8f579..8bb67eefb1 100644 --- a/pkg/console/global.go +++ b/pkg/console/global.go @@ -24,28 +24,53 @@ func SetColor(color bool) { } // Debug level message. -func Debug(msg string, v ...interface{}) { - ConsoleInstance.Debug(msg, v...) +func Debug(msg string) { + ConsoleInstance.Debug(msg) } // Info level message. -func Info(msg string, v ...interface{}) { - ConsoleInstance.Info(msg, v...) +func Info(msg string) { + ConsoleInstance.Info(msg) } // Warn level message. -func Warn(msg string, v ...interface{}) { - ConsoleInstance.Warn(msg, v...) +func Warn(msg string) { + ConsoleInstance.Warn(msg) } // Error level message. -func Error(msg string, v ...interface{}) { - ConsoleInstance.Error(msg, v...) +func Error(msg string) { + ConsoleInstance.Error(msg) } // Fatal level message. -func Fatal(msg string, v ...interface{}) { - ConsoleInstance.Fatal(msg, v...) +func Fatal(msg string) { + ConsoleInstance.Fatal(msg) +} + +// Debug level message. +func Debugf(msg string, v ...interface{}) { + ConsoleInstance.Debugf(msg, v...) +} + +// Info level message. +func Infof(msg string, v ...interface{}) { + ConsoleInstance.Infof(msg, v...) +} + +// Warn level message. +func Warnf(msg string, v ...interface{}) { + ConsoleInstance.Warnf(msg, v...) +} + +// Error level message. +func Errorf(msg string, v ...interface{}) { + ConsoleInstance.Errorf(msg, v...) +} + +// Fatal level message. +func Fatalf(msg string, v ...interface{}) { + ConsoleInstance.Fatalf(msg, v...) } // Output a line to stdout. Useful for printing primary output of a command, or the output of a subcommand. diff --git a/pkg/console/interactive.go b/pkg/console/interactive.go index ca44efb921..b3288dd7f2 100644 --- a/pkg/console/interactive.go +++ b/pkg/console/interactive.go @@ -65,7 +65,7 @@ func (i Interactive) Read() (string, error) { if i.Options != nil { if !slices.ContainsString(i.Options, text) { - Warn("%s is not a valid option", text) + Warnf("%s is not a valid option", text) continue } } diff --git a/pkg/docker/cog.py b/pkg/docker/cog.py index 9819fdd9a8..448a2718b2 100644 --- a/pkg/docker/cog.py +++ b/pkg/docker/cog.py @@ -11,6 +11,7 @@ from abc import ABC, abstractmethod from pathlib import Path from typing import Optional, Any, Type, List, Callable, Dict +from numbers import Number from flask import Flask, send_file, request, jsonify, abort, Response from werkzeug.datastructures import FileStorage @@ -99,6 +100,10 @@ def help(): arg["help"] = spec.help if spec.default is not _UNSPECIFIED: arg["default"] = str(spec.default) # TODO: don't string this + if spec.min is not None: + arg["min"] = str(spec.min) # TODO: don't string this + if spec.max is not None: + arg["max"] = str(spec.max) # TODO: don't string this args[name] = arg return jsonify({"arguments": args}) @@ -179,6 +184,12 @@ def validate_and_convert_inputs( f"Internal error: Input type {input_spec} is not a valid input type" ) + if _is_numeric_type(input_spec.type): + if input_spec.max is not None and converted > input_spec.max: + raise InputValidationError(f"Value {converted} is greater than the max value {input_spec.max}") + if input_spec.min is not None and converted < input_spec.min: + raise InputValidationError(f"Value {converted} is less than the min value {input_spec.min}") + else: if input_spec.default is not _UNSPECIFIED: converted = input_spec.default @@ -205,25 +216,34 @@ def unzip_to_tempdir(zip_path): def make_temp_path(filename): + temp_dir = make_temp_dir() + return Path(os.path.join(temp_dir, filename)) + + +def make_temp_dir(): # TODO(andreas): cleanup - tempdir = tempfile.mkdtemp() - return Path(os.path.join(tempdir, filename)) + temp_dir = tempfile.mkdtemp() + return temp_dir @dataclass class InputSpec: type: Type default: Any = _UNSPECIFIED + min: Optional[Number] = None + max: Optional[Number] = None help: Optional[str] = None -def input(name, type, default=_UNSPECIFIED, help=None): +def input(name, type, default=_UNSPECIFIED, min=None, max=None, help=None): + type_name = _type_name(type) if type not in _VALID_INPUT_TYPES: - type_name = _type_name(type) type_list = ", ".join([_type_name(t) for t in _VALID_INPUT_TYPES]) raise ValueError( f"{type_name} is not a valid input type. Valid types are: {type_list}" ) + if (min is not None or max is not None) and not _is_numeric_type(type): + raise ValueError(f"Non-numeric type {type_name} cannot have min and max values") def wrapper(f): if not hasattr(f, "_inputs"): @@ -235,7 +255,9 @@ def wrapper(f): if type == Path and default is not _UNSPECIFIED and default is not None: raise TypeError("Cannot use default with Path type") - f._inputs[name] = InputSpec(type=type, default=default, help=help) + f._inputs[name] = InputSpec( + type=type, default=default, min=min, max=max, help=help + ) @functools.wraps(f) def wraps(self, **kwargs): @@ -248,18 +270,22 @@ def wraps(self, **kwargs): return wrapper -def _type_name(type: Type) -> str: - if type == str: +def _type_name(typ: Type) -> str: + if typ == str: return "str" - if type == int: + if typ == int: return "int" - if type == float: + if typ == float: return "float" - if type == bool: + if typ == bool: return "bool" - if type == Path: + if typ == Path: return "Path" - return str(type) + return str(typ) + + +def _is_numeric_type(typ: Type) -> bool: + return typ in (int, float) def _method_arg_names(f) -> List[str]: diff --git a/pkg/docker/cog_test.py b/pkg/docker/cog_test.py index efdf6855ce..72c3e7bc69 100644 --- a/pkg/docker/cog_test.py +++ b/pkg/docker/cog_test.py @@ -167,6 +167,29 @@ def run(self, num): assert resp.status_code == 400 +def test_min_max(): + class Model(cog.Model): + def setup(self): + pass + + @cog.input("num1", type=float, min=3, max=10.5) + @cog.input("num2", type=float, min=-4) + @cog.input("num3", type=int, max=-4) + def run(self, num1, num2, num3): + return num1 + num2 + num3 + + client = make_client(Model()) + resp = client.post("/infer", data={"num1": 3, "num2": -4, "num3":-4}) + assert resp.status_code == 200 + assert resp.data == b"-5.0\n" + resp = client.post("/infer", data={"num1": 2, "num2": -4, "num3":-4}) + assert resp.status_code == 400 + resp = client.post("/infer", data={"num1": 3, "num2": -4.1, "num3":-4}) + assert resp.status_code == 400 + resp = client.post("/infer", data={"num1": 3, "num2": -4, "num3":-3}) + assert resp.status_code == 400 + + def test_good_path_input(): class Model(cog.Model): def setup(self): diff --git a/pkg/docker/generate.go b/pkg/docker/generate.go index 15c803278e..18ebe061d8 100644 --- a/pkg/docker/generate.go +++ b/pkg/docker/generate.go @@ -1,5 +1,7 @@ package docker +// TODO(andreas): allow files to be edited without re-running the subsequent post_install scripts (hard!) + import ( _ "embed" "encoding/base64" @@ -22,6 +24,8 @@ const ( SectionInstallingPythonPackages = "Installing Python packages" SectionInstallingCog = "Installing Cog" SectionCopyingCode = "Copying code" + SectionPreInstall = "Running pre-install script" + SectionPostInstall = "Running post-install script" ) //go:embed cog.py @@ -37,7 +41,6 @@ func (g *DockerfileGenerator) Generate() (string, error) { if err != nil { return "", err } - preamble := g.preamble() installPython, err := g.installPython() if err != nil { return "", err @@ -56,13 +59,16 @@ func (g *DockerfileGenerator) Generate() (string, error) { } return strings.Join(filterEmpty([]string{ "FROM " + baseImage, - preamble, + g.preamble(), installPython, aptInstalls, pythonRequirements, pipInstalls, g.installCog(), + g.preInstall(), g.copyCode(), + g.workdir(), + g.postInstall(), g.command(), }), "\n"), nil } @@ -72,19 +78,15 @@ func (g *DockerfileGenerator) baseImage() (string, error) { case "cpu": return "ubuntu:20.04", nil case "gpu": - return g.gpuBaseImage() + return g.Config.CUDABaseImageTag() } return "", fmt.Errorf("Invalid architecture: %s", g.Arch) } func (g *DockerfileGenerator) preamble() string { // TODO: other stuff - return "ENV DEBIAN_FRONTEND=noninteractive" -} - -func (g *DockerfileGenerator) gpuBaseImage() (string, error) { - // TODO: return correct ubuntu version for tf / torch - return "nvidia/cuda:11.0-devel-ubuntu20.04", nil + return `ENV DEBIAN_FRONTEND=noninteractive +ENV LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/lib/x86_64-linux-gnu` } func (g *DockerfileGenerator) aptInstalls() (string, error) { @@ -171,8 +173,7 @@ func (g *DockerfileGenerator) pipInstalls() (string, error) { } func (g *DockerfileGenerator) copyCode() string { - return g.sectionLabel(SectionCopyingCode) + `COPY . /code -WORKDIR /code` + return g.sectionLabel(SectionCopyingCode) + `COPY . /code` } func (g *DockerfileGenerator) command() string { @@ -185,6 +186,32 @@ func (g *DockerfileGenerator) command() string { return `CMD ["python", "-c", "from ` + module + ` import ` + class + `; ` + class + `().start_server()"]` } +func (g *DockerfileGenerator) workdir() string { + wd := "/code" + if g.Config.Environment.Workdir != "" { + wd += "/" + g.Config.Environment.Workdir + } + return "WORKDIR " + wd +} + +func (g *DockerfileGenerator) preInstall() string { + lines := []string{} + for _, run := range g.Config.Environment.PreInstall { + run = strings.TrimSpace(run) + lines = append(lines, g.sectionLabel(SectionPreInstall+" "+run)+"RUN "+run) + } + return strings.Join(lines, "\n") +} + +func (g *DockerfileGenerator) postInstall() string { + lines := []string{} + for _, run := range g.Config.Environment.PostInstall { + run = strings.TrimSpace(run) + lines = append(lines, g.sectionLabel(SectionPostInstall+" "+run)+"RUN "+run) + } + return strings.Join(lines, "\n") +} + func (g *DockerfileGenerator) sectionLabel(label string) string { return fmt.Sprintf("RUN %s%s\n", SectionPrefix, label) } diff --git a/pkg/docker/generate_test.go b/pkg/docker/generate_test.go index de652636f6..26b7f13fa1 100644 --- a/pkg/docker/generate_test.go +++ b/pkg/docker/generate_test.go @@ -55,17 +55,20 @@ func TestGenerateEmpty(t *testing.T) { model: infer.py:Model `)) require.NoError(t, err) + require.NoError(t, conf.ValidateAndCompleteConfig()) expectedCPU := `FROM ubuntu:20.04 ENV DEBIAN_FRONTEND=noninteractive +ENV LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/lib/x86_64-linux-gnu ` + installPython("3.8") + installCog() + ` RUN ### --> Copying code COPY . /code WORKDIR /code CMD ["python", "-c", "from infer import Model; Model().start_server()"]` - expectedGPU := `FROM nvidia/cuda:11.0-devel-ubuntu20.04 + expectedGPU := `FROM nvidia/cuda:11.0-cudnn8-devel-ubuntu16.04 ENV DEBIAN_FRONTEND=noninteractive +ENV LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/lib/x86_64-linux-gnu ` + installPython("3.8") + installCog() + ` RUN ### --> Copying code COPY . /code @@ -96,12 +99,11 @@ environment: model: infer.py:Model `)) require.NoError(t, err) - - err = conf.ValidateAndCompleteConfig() - require.NoError(t, err) + require.NoError(t, conf.ValidateAndCompleteConfig()) expectedCPU := `FROM ubuntu:20.04 ENV DEBIAN_FRONTEND=noninteractive +ENV LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/lib/x86_64-linux-gnu ` + installPython("3.8") + `RUN ### --> Installing system packages RUN apt-get update -qq && apt-get install -qy ffmpeg cowsay && rm -rf /var/lib/apt/lists/* RUN ### --> Installing Python requirements @@ -115,8 +117,9 @@ COPY . /code WORKDIR /code CMD ["python", "-c", "from infer import Model; Model().start_server()"]` - expectedGPU := `FROM nvidia/cuda:11.0-devel-ubuntu20.04 + expectedGPU := `FROM nvidia/cuda:10.2-cudnn8-devel-ubuntu18.04 ENV DEBIAN_FRONTEND=noninteractive +ENV LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/lib/x86_64-linux-gnu ` + installPython("3.8") + `RUN ### --> Installing system packages RUN apt-get update -qq && apt-get install -qy ffmpeg cowsay && rm -rf /var/lib/apt/lists/* RUN ### --> Installing Python requirements diff --git a/pkg/docker/local_builder.go b/pkg/docker/local_builder.go index 9c50327efb..3386013ecb 100644 --- a/pkg/docker/local_builder.go +++ b/pkg/docker/local_builder.go @@ -45,18 +45,19 @@ func (b *LocalImageBuilder) BuildAndPush(dir string, dockerfilePath string, name } func (b *LocalImageBuilder) build(dir string, dockerfilePath string, logWriter logger.Logger) (tag string, err error) { - console.Debug("Building in %s", dir) + console.Debugf("Building in %s", dir) cmd := exec.Command( "docker", "build", ".", "--progress", "plain", "-f", dockerfilePath, - "--build-arg", "BUILDKIT_INLINE_CACHE=1", + // "--build-arg", "BUILDKIT_INLINE_CACHE=1", ) cmd.Dir = dir - cmd.Env = append(os.Environ(), "DOCKER_BUILDKIT=1") + // TODO(andreas): follow https://github.com/moby/buildkit/issues/1436, hopefully buildkit will be able to use GPUs soon + cmd.Env = append(os.Environ(), "DOCKER_BUILDKIT=0") - lastLogsChan, tagChan, err := buildPipe(cmd.StderrPipe, logWriter) + lastLogsChan, tagChan, err := buildPipe(cmd.StdoutPipe, logWriter) if err != nil { return "", err } @@ -68,20 +69,20 @@ func (b *LocalImageBuilder) build(dir string, dockerfilePath string, logWriter l if err = cmd.Wait(); err != nil { lastLogs := <-lastLogsChan for _, logLine := range lastLogs { - logWriter.WriteLogLine(logLine) + logWriter.Info(logLine) } return "", err } dockerTag := <-tagChan - logWriter.WriteLogLine("Successfully built %s", dockerTag) + logWriter.Infof("Successfully built %s", dockerTag) return dockerTag, err } func (b *LocalImageBuilder) tag(tag string, fullImageTag string, logWriter logger.Logger) error { - console.Debug("Tagging %s as %s", tag, fullImageTag) + console.Debugf("Tagging %s as %s", tag, fullImageTag) cmd := exec.Command("docker", "tag", tag, fullImageTag) cmd.Env = os.Environ() @@ -94,7 +95,7 @@ func (b *LocalImageBuilder) tag(tag string, fullImageTag string, logWriter logge } func (b *LocalImageBuilder) push(tag string, logWriter logger.Logger) error { - logWriter.WriteLogLine("Pushing %s to registry", tag) + logWriter.Infof("Pushing %s to registry", tag) args := []string{"push", tag} cmd := exec.Command("docker", args...) @@ -124,8 +125,6 @@ func buildPipe(pf shell.PipeFunc, logWriter logger.Logger) (lastLogsChan chan [] // we look for "LABEL" first. obviously this requires // all LABELs to be at the end of the build script. - hasSeenLabel := false - label := " : LABEL" successPrefix := "Successfully built " sectionPrefix := "RUN " + SectionPrefix buildkitRegex := regexp.MustCompile("^#[0-9]+ writing image sha256:([0-9a-f]{12}).+$") @@ -141,23 +140,21 @@ func buildPipe(pf shell.PipeFunc, logWriter logger.Logger) (lastLogsChan chan [] go func() { currentSection := SectionStartingBuild currentLogLines := []string{} + for scanner.Scan() { line := scanner.Text() - logWriter.WriteDebugLine(line) + logWriter.Debug(line) if strings.Contains(line, sectionPrefix) { currentSection = strings.SplitN(line, sectionPrefix, 2)[1] currentLogLines = []string{} - logWriter.WriteLogLine(fmt.Sprintf(" * %s", currentSection)) + logWriter.Infof(" * %s", currentSection) } else { currentLogLines = append(currentLogLines, line) } - if hasSeenLabel && strings.HasPrefix(line, successPrefix) { + if strings.HasPrefix(line, successPrefix) { tagChan <- strings.TrimSpace(strings.TrimPrefix(line, successPrefix)) } - if !hasSeenLabel && strings.Contains(line, label) { - hasSeenLabel = true - } match := buildkitRegex.FindStringSubmatch(line) if len(match) == 2 { tagChan <- match[1] @@ -179,7 +176,7 @@ func pipeToWithDockerChecks(pf shell.PipeFunc, logWriter logger.Logger) (done ch console.Fatal("Your Docker version appears to be out out date; please upgrade Docker to the latest version and try again") } if logWriter != nil { - logWriter.WriteLogLine(line) + logWriter.Info(line) } }) } diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index aa5d31336f..be0d270205 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -7,8 +7,10 @@ import ( ) type Logger interface { - WriteLogLine(line string, args ...interface{}) - WriteDebugLine(line string, args ...interface{}) + Info(line string) + Debug(line string) + Infof(line string, args ...interface{}) + Debugf(line string, args ...interface{}) WriteStatus(status string, args ...interface{}) WriteError(err error) WriteModel(mod *model.Model) @@ -21,16 +23,24 @@ func NewConsoleLogger() *ConsoleLogger { return new(ConsoleLogger) } -func (l *ConsoleLogger) WriteLogLine(line string, args ...interface{}) { - console.Info(line, args...) +func (l *ConsoleLogger) Info(line string) { + console.Info(line) } -func (l *ConsoleLogger) WriteDebugLine(line string, args ...interface{}) { - console.Debug(line, args...) +func (l *ConsoleLogger) Debug(line string) { + console.Debug(line) +} + +func (l *ConsoleLogger) Infof(line string, args ...interface{}) { + console.Infof(line, args...) +} + +func (l *ConsoleLogger) Debugf(line string, args ...interface{}) { + console.Debugf(line, args...) } func (l *ConsoleLogger) WriteStatus(status string, args ...interface{}) { - console.Info(status, args...) + console.Infof(status, args...) } func (l *ConsoleLogger) WriteError(err error) { @@ -38,5 +48,5 @@ func (l *ConsoleLogger) WriteError(err error) { } func (l *ConsoleLogger) WriteModel(mod *model.Model) { - console.Info("%v", mod) + console.Infof("%v", mod) } diff --git a/pkg/logger/stream_logger.go b/pkg/logger/stream_logger.go index 44b3dc594a..c7330e8d6c 100644 --- a/pkg/logger/stream_logger.go +++ b/pkg/logger/stream_logger.go @@ -39,7 +39,7 @@ func (logger *StreamLogger) logText(messageType MessageType, text string) { msg := Message{Type: messageType, Text: text} data, err := json.Marshal(msg) if err != nil { - console.Warn("Failed to marshal console text: %s", text) + console.Warnf("Failed to marshal console text: %s", text) } logger.write(data) } @@ -49,7 +49,7 @@ func (logger *StreamLogger) write(data []byte) { logger.mu.Lock() defer logger.mu.Unlock() if _, err := logger.writer.Write(data); err != nil { - console.Warn("HTTP response writer failed to write: %s", data) + console.Warnf("HTTP response writer failed to write: %s", data) return } if f, ok := logger.writer.(http.Flusher); ok { @@ -59,11 +59,19 @@ func (logger *StreamLogger) write(data []byte) { } } -func (logger *StreamLogger) WriteLogLine(line string, args ...interface{}) { +func (logger *StreamLogger) Info(line string) { + logger.logText(MessageTypeLogLine, line) +} + +func (logger *StreamLogger) Debug(line string) { + logger.logText(MessageTypeDebugLine, line) +} + +func (logger *StreamLogger) Infof(line string, args ...interface{}) { logger.logText(MessageTypeLogLine, fmt.Sprintf(line, args...)) } -func (logger *StreamLogger) WriteDebugLine(line string, args ...interface{}) { +func (logger *StreamLogger) Debugf(line string, args ...interface{}) { logger.logText(MessageTypeDebugLine, fmt.Sprintf(line, args...)) } diff --git a/pkg/model/compatibility.go b/pkg/model/compatibility.go index b7f76fa7e2..ce28b2b2e0 100644 --- a/pkg/model/compatibility.go +++ b/pkg/model/compatibility.go @@ -12,6 +12,11 @@ import ( "github.com/replicate/cog/pkg/version" ) +// TODO(andreas): check tf/py versions. tf 1.5.0 didn't install on py 3.8 +// TODO(andreas): support more tf versions. No matching tensorflow CPU package for version 1.15.4, etc. +// TODO(andreas): allow user to install versions that aren't compatible +// TODO(andreas): allow user to install tf cpu package on gpu + type TFCompatibility struct { TF string TFCPUPackage string @@ -105,13 +110,13 @@ var CUDABaseImages []CUDABaseImage func init() { if err := json.Unmarshal(tfCompatibilityMatrixData, &TFCompatibilityMatrix); err != nil { - console.Fatal("Failed to load embedded Tensorflow compatibility matrix: %s", err) + console.Fatalf("Failed to load embedded Tensorflow compatibility matrix: %s", err) } if err := json.Unmarshal(torchCompatibilityMatrixData, &TorchCompatibilityMatrix); err != nil { - console.Fatal("Failed to load embedded PyTorch compatibility matrix: %s", err) + console.Fatalf("Failed to load embedded PyTorch compatibility matrix: %s", err) } if err := json.Unmarshal(cudaBaseImageTagsData, &CUDABaseImages); err != nil { - console.Fatal("Failed to load embedded CUDA base images: %s", err) + console.Fatalf("Failed to load embedded CUDA base images: %s", err) } } @@ -186,10 +191,11 @@ func latestCuDNNForCUDA(cuda string) string { func latestTF() TFCompatibility { var latest *TFCompatibility for _, compat := range TFCompatibilityMatrix { + compat := compat if latest == nil { latest = &compat } else { - greater, err := versionGreater(latest.TF, compat.TF) + greater, err := versionGreater(compat.TF, latest.TF) if err != nil { // should never happen panic(fmt.Sprintf("Invalid tensorflow version: %s", err)) @@ -252,12 +258,40 @@ func torchCPUPackage(ver string) (name string, cpuVersion string, indexURL strin } func torchGPUPackage(ver string, cuda string) (name string, cpuVersion string, indexURL string, err error) { + // find the torch package that has the requested torch version and the latest cuda version + // that is at most as high as the requested cuda version + var latest *TorchCompatibility for _, compat := range TorchCompatibilityMatrix { - if compat.TorchVersion() == ver && compat.CUDA != nil && *compat.CUDA == cuda { - return "torch", compat.Torch, compat.IndexURL, nil + compat := compat + if compat.TorchVersion() != ver || compat.CUDA == nil { + continue + } + greater, err := versionGreater(*compat.CUDA, cuda) + if err != nil { + panic(fmt.Sprintf("Invalid CUDA version: %s", err)) + } + + if greater { + continue } + if latest == nil { + latest = &compat + } else { + greater, err := versionGreater(*compat.CUDA, *latest.CUDA) + if err != nil { + // should never happen + panic(fmt.Sprintf("Invalid CUDA version: %s", err)) + } + if greater { + latest = &compat + } + } + } + if latest == nil { + return "", "", "", fmt.Errorf("No torch GPU package for version %s that's lower or equal to CUDA %s", ver, cuda) } - return "", "", "", fmt.Errorf("No matching torch GPU package for version %s and CUDA %s", ver, cuda) + + return "torch", latest.Torch, latest.IndexURL, nil } func torchvisionCPUPackage(ver string) (name string, cpuVersion string, indexURL string, err error) { @@ -270,10 +304,38 @@ func torchvisionCPUPackage(ver string) (name string, cpuVersion string, indexURL } func torchvisionGPUPackage(ver string, cuda string) (name string, cpuVersion string, indexURL string, err error) { + // find the torchvision package that has the requested + // torchvision version and the latest cuda version that is at + // most as high as the requested cuda version + var latest *TorchCompatibility for _, compat := range TorchCompatibilityMatrix { - if compat.TorchvisionVersion() == ver && *compat.CUDA == cuda { - return "torchvision", compat.Torchvision, compat.IndexURL, nil + compat := compat + if compat.TorchvisionVersion() != ver || compat.CUDA == nil { + continue + } + greater, err := versionGreater(*compat.CUDA, cuda) + if err != nil { + panic(fmt.Sprintf("Invalid CUDA version: %s", err)) + } + if greater { + continue + } + if latest == nil { + latest = &compat + } else { + greater, err := versionGreater(*compat.CUDA, *latest.CUDA) + if err != nil { + // should never happen + panic(fmt.Sprintf("Invalid CUDA version: %s", err)) + } + if greater { + latest = &compat + } } } - return "", "", "", fmt.Errorf("No matching torchvision GPU package for version %s and CUDA %s", ver, cuda) + if latest == nil { + return "", "", "", fmt.Errorf("No torchvision GPU package for version %s that's lower or equal to CUDA %s", ver, cuda) + } + + return "torchvision", latest.Torchvision, latest.IndexURL, nil } diff --git a/pkg/model/config.go b/pkg/model/config.go index 98899ad725..849817a9da 100644 --- a/pkg/model/config.go +++ b/pkg/model/config.go @@ -22,6 +22,9 @@ type Environment struct { PythonFindLinks []string `json:"python_find_links" yaml:"python_find_links"` PythonPackages []string `json:"python_packages" yaml:"python_packages"` SystemPackages []string `json:"system_packages" yaml:"system_packages"` + Workdir string `json:"workdir" yaml:"workdir"` + PreInstall []string `json:"pre_install" yaml:"pre_install"` + PostInstall []string `json:"post_install" yaml:"post_install"` Architectures []string `json:"architectures" yaml:"architectures"` CUDA string `json:"cuda" yaml:"cuda"` CuDNN string `json:"cudnn" yaml:"cudnn"` @@ -95,7 +98,7 @@ func (c *Config) pythonPackageVersion(name string) (version string, ok bool) { pkgName, version, err := splitPythonPackage(pkg) if err != nil { // this should be caught by validation earlier - console.Warn("Python package %s is malformed.", pkg) + console.Warnf("Python package %s is malformed.", pkg) return "", false } if pkgName == name { @@ -221,64 +224,43 @@ Compatible CuDNN versions are: %s`, c.Environment.CUDA, c.Environment.CuDNN, str if err != nil { return err } - var cudas []string - var cuDNN string + // The pre-compiled TensorFlow binaries requires specific CUDA/CuDNN versions to be + // installed, but Torch bundles their own CUDA/CuDNN libraries. - if torchVersion != "" && tfVersion != "" { - // both torch and tensorflow are used - if sliceContains(torchCUDAs, tfCUDA) { - cudas = []string{tfCUDA} - } else { - return fmt.Errorf(`Incompatible CUDA versions for the specified tensorflow and torch versions. -tensorflow==%s works with CUDA %s; torch==%s works with CUDA %s"`, - tfVersion, tfCUDA, - torchVersion, strings.Join(torchCUDAs, ",")) - } - if c.Environment.CUDA != "" && !sliceContains(cudas, c.Environment.CUDA) { - return fmt.Errorf(`The specified CUDA version %s is not compatible with tensorflow==%s and torch==%s. -Compatible CUDA versions are: %s`, - c.Environment.CUDA, tfVersion, torchVersion, strings.Join(cudas, ",")) - } - } else if torchVersion != "" { - // only torch is set - cudas = torchCUDAs - if c.Environment.CUDA != "" && !sliceContains(cudas, c.Environment.CUDA) { - return fmt.Errorf(`The specified CUDA version %s is not compatible with torch==%s. -Compatible CUDA versions are: %s`, - c.Environment.CUDA, torchVersion, strings.Join(cudas, ",")) - } - } else if tfVersion != "" { - // only tensorflow is set - cudas = []string{tfCUDA} - if c.Environment.CUDA != "" && !sliceContains(cudas, c.Environment.CUDA) { + if tfVersion != "" { + if c.Environment.CUDA == "" { + console.Infof("Setting CUDA to version %s from Tensorflow version", tfCUDA) + c.Environment.CUDA = tfCUDA + } else if tfCUDA != c.Environment.CUDA { return fmt.Errorf(`The specified CUDA version %s is not compatible with tensorflow==%s. -Compatible CUDA versions are: %s`, - c.Environment.CUDA, tfVersion, strings.Join(cudas, ",")) +Compatible CUDA version is: %s`, + c.Environment.CUDA, tfVersion, tfCUDA) } - if c.Environment.CuDNN != "" && c.Environment.CuDNN != tfCuDNN { + if c.Environment.CuDNN == "" { + console.Infof("Setting CuDNN to version %s from Tensorflow version", tfCuDNN) + c.Environment.CuDNN = tfCuDNN + } else if tfCuDNN != c.Environment.CuDNN { return fmt.Errorf(`The specified cuDNN version %s is not compatible with tensorflow==%s. Compatible cuDNN version is: %s`, c.Environment.CuDNN, tfVersion, tfCuDNN) } - cuDNN = tfCuDNN - } - - if c.Environment.CUDA == "" { - if len(cudas) == 0 { + } else if torchVersion != "" { + if c.Environment.CUDA == "" { + c.Environment.CUDA = latestCUDAFrom(torchCUDAs) + console.Infof("Setting CUDA to version %s from Torch version", c.Environment.CUDA) + } + if c.Environment.CuDNN == "" { + c.Environment.CuDNN = latestCuDNNForCUDA(c.Environment.CUDA) + console.Infof("Setting CuDNN to version %s", c.Environment.CUDA) + } + } else { + if c.Environment.CUDA == "" { c.Environment.CUDA = defaultCUDA() - console.Info("Setting CUDA to version %s", c.Environment.CUDA) - } else { - c.Environment.CUDA = latestCUDAFrom(cudas) - console.Info("Setting CUDA to version %s from torch/tensorflow version", c.Environment.CUDA) + console.Infof("Setting CUDA to version %s", c.Environment.CUDA) } - } - if c.Environment.CuDNN == "" { - if cuDNN == "" { + if c.Environment.CuDNN == "" { c.Environment.CuDNN = latestCuDNNForCUDA(c.Environment.CUDA) - console.Info("Setting CuDNN to version %s", c.Environment.CuDNN) - } else { - c.Environment.CuDNN = cuDNN - console.Info("Setting CuDNN to version %s from torch/tensorflow version", c.Environment.CuDNN) + console.Infof("Setting CuDNN to version %s", c.Environment.CUDA) } } diff --git a/pkg/model/cuda_base_image_tags.json b/pkg/model/cuda_base_image_tags.json index 5182f0c72c..a33e64ba61 100644 --- a/pkg/model/cuda_base_image_tags.json +++ b/pkg/model/cuda_base_image_tags.json @@ -45,4 +45,4 @@ "7.0-cudnn4-devel-ubuntu14.04", "7.5-cudnn3-devel-ubuntu14.04", "7.0-cudnn2-devel-ubuntu14.04" -] \ No newline at end of file +] diff --git a/pkg/model/model.go b/pkg/model/model.go index 47170edacb..b924774838 100644 --- a/pkg/model/model.go +++ b/pkg/model/model.go @@ -2,8 +2,6 @@ package model import "time" -type Target string - const ( TargetDockerCPU = "docker-cpu" TargetDockerGPU = "docker-gpu" @@ -18,7 +16,7 @@ type Model struct { } type Artifact struct { - Target Target `json:"target"` + Target string `json:"target"` URI string `json:"uri"` } @@ -35,10 +33,12 @@ const ( type RunArgument struct { Type ArgumentType `json:"type"` Default *string `json:"default"` + Min *string `json:"min"` + Max *string `json:"max"` Help *string `json:"help"` } -func (m *Model) ArtifactFor(target Target) (artifact *Artifact, ok bool) { +func (m *Model) ArtifactFor(target string) (artifact *Artifact, ok bool) { for _, a := range m.Artifacts { if a.Target == target { return a, true diff --git a/pkg/model/torch_compatability_matrix.json b/pkg/model/torch_compatability_matrix.json index db880f5f16..4811535b50 100644 --- a/pkg/model/torch_compatability_matrix.json +++ b/pkg/model/torch_compatability_matrix.json @@ -402,4 +402,4 @@ "3.9" ] } -] \ No newline at end of file +] diff --git a/pkg/server/build.go b/pkg/server/build.go index 3384d53833..c44833dd2b 100644 --- a/pkg/server/build.go +++ b/pkg/server/build.go @@ -114,7 +114,16 @@ func (s *Server) ReceiveModel(r *http.Request, streamLogger *logger.StreamLogger func (s *Server) testModel(mod *model.Model, dir string, logWriter logger.Logger) (map[string]*model.RunArgument, error) { logWriter.WriteStatus("Testing model") - deployment, err := s.servingPlatform.Deploy(mod, model.TargetDockerCPU, logWriter) + target := model.TargetDockerCPU + if _, ok := mod.ArtifactFor(model.TargetDockerCPU); !ok { + if _, ok := mod.ArtifactFor(model.TargetDockerGPU); ok { + target = model.TargetDockerGPU + } else { + return nil, fmt.Errorf("Model has neither CPU or GPU target") + } + } + + deployment, err := s.servingPlatform.Deploy(mod, target, logWriter) if err != nil { return nil, err } @@ -140,7 +149,7 @@ func (s *Server) testModel(mod *model.Model, dir string, logWriter logger.Logger if err != nil { return nil, fmt.Errorf("Failed to read output: %w", err) } - logWriter.WriteLogLine(fmt.Sprintf("Inference result length: %d, mime type: %s", len(outputBytes), output.MimeType)) + logWriter.Infof(fmt.Sprintf("Inference result length: %d, mime type: %s", len(outputBytes), output.MimeType)) if example.Output != "" && strings.TrimSpace(string(outputBytes)) != example.Output { return nil, fmt.Errorf("Output %s doesn't match expected: %s", outputBytes, example.Output) } @@ -173,7 +182,7 @@ func (s *Server) buildDockerImages(dir string, config *model.Config, name string if err != nil { return nil, fmt.Errorf("Failed to build Docker image: %w", err) } - var target model.Target + var target string switch arch { case "cpu": target = model.TargetDockerCPU diff --git a/pkg/server/delete.go b/pkg/server/delete.go index 4d172120a4..91d3657a73 100644 --- a/pkg/server/delete.go +++ b/pkg/server/delete.go @@ -8,7 +8,7 @@ import ( func (s *Server) DeleteModel(w http.ResponseWriter, r *http.Request) { user, name, id := getRepoVars(r) - console.Info("Received delete request for %s/%s/%s", user, name, id) + console.Infof("Received delete request for %s/%s/%s", user, name, id) mod, err := s.db.GetModel(user, name, id) if err != nil { diff --git a/pkg/server/download.go b/pkg/server/download.go index b3808e08aa..d6ba07beb0 100644 --- a/pkg/server/download.go +++ b/pkg/server/download.go @@ -10,7 +10,7 @@ import ( func (s *Server) DownloadModel(w http.ResponseWriter, r *http.Request) { user, name, id := getRepoVars(r) - console.Info("Received download request for %s/%s/%s", user, name, id) + console.Infof("Received download request for %s/%s/%s", user, name, id) modTime := time.Now() // TODO mod, err := s.db.GetModel(user, name, id) @@ -30,6 +30,6 @@ func (s *Server) DownloadModel(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) return } - console.Info("Downloaded %d bytes", len(content)) + console.Infof("Downloaded %d bytes", len(content)) http.ServeContent(w, r, id+".zip", modTime, bytes.NewReader(content)) } diff --git a/pkg/server/get.go b/pkg/server/get.go index 4a7ee3d140..bbaa59610a 100644 --- a/pkg/server/get.go +++ b/pkg/server/get.go @@ -9,7 +9,7 @@ import ( func (s *Server) SendModelMetadata(w http.ResponseWriter, r *http.Request) { user, name, id := getRepoVars(r) - console.Info("Received get request for %s/%s/%s", user, name, id) + console.Infof("Received get request for %s/%s/%s", user, name, id) mod, err := s.db.GetModel(user, name, id) if err != nil { diff --git a/pkg/server/list.go b/pkg/server/list.go index 5ff5d497b5..3d451a1653 100644 --- a/pkg/server/list.go +++ b/pkg/server/list.go @@ -9,7 +9,7 @@ import ( func (s *Server) ListModels(w http.ResponseWriter, r *http.Request) { user, name, _ := getRepoVars(r) - console.Info("Received list request for %s%s", user, name) + console.Infof("Received list request for %s%s", user, name) models, err := s.db.ListModels(user, name) if err != nil { diff --git a/pkg/server/server.go b/pkg/server/server.go index 063c825f18..0f7b81256f 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -59,7 +59,7 @@ func (s *Server) Start() error { router.Path("/v1/repos/{user}/{name}/models/{id}"). Methods(http.MethodDelete). HandlerFunc(s.DeleteModel) - console.Info("Server running on 0.0.0.0:%d", s.port) + console.Infof("Server running on 0.0.0.0:%d", s.port) return http.ListenAndServe(fmt.Sprintf(":%d", s.port), router) } diff --git a/pkg/serving/local.go b/pkg/serving/local.go index faaa9049a7..069ac28e54 100644 --- a/pkg/serving/local.go +++ b/pkg/serving/local.go @@ -47,7 +47,7 @@ func NewLocalDockerPlatform() (*LocalDockerPlatform, error) { }, nil } -func (p *LocalDockerPlatform) Deploy(mod *model.Model, target model.Target, logWriter logger.Logger) (Deployment, error) { +func (p *LocalDockerPlatform) Deploy(mod *model.Model, target string, logWriter logger.Logger) (Deployment, error) { // TODO(andreas): output container logs artifact, ok := mod.ArtifactFor(target) @@ -56,7 +56,7 @@ func (p *LocalDockerPlatform) Deploy(mod *model.Model, target model.Target, logW } imageTag := artifact.URI - logWriter.WriteLogLine("Deploying container %s for target %s", imageTag, artifact.Target) + logWriter.Infof("Deploying container %s for target %s", imageTag, artifact.Target) if !docker.Exists(imageTag, logWriter) { if err := docker.Pull(imageTag, logWriter); err != nil { @@ -126,7 +126,7 @@ func (p *LocalDockerPlatform) waitForContainerReady(hostPort int, containerID st url := fmt.Sprintf("http://localhost:%d/ping", hostPort) start := time.Now() - logWriter.WriteLogLine("Waiting for model container to become accessible") + logWriter.Infof("Waiting for model container to become accessible") for { now := time.Now() if now.Sub(start) > global.StartupTimeout { @@ -150,7 +150,7 @@ func (p *LocalDockerPlatform) waitForContainerReady(hostPort int, containerID st if resp.StatusCode != http.StatusOK { continue } - logWriter.WriteLogLine("Got successful ping response from container") + logWriter.Infof("Got successful ping response from container") return nil } } @@ -230,14 +230,14 @@ func (d *LocalDockerDeployment) RunInference(input *Example, logWriter logger.Lo if setupTimeStr != "" { setupTime, err = strconv.ParseFloat(setupTimeStr, 64) if err != nil { - console.Error("Failed to parse setup time '%s' as float: %s", setupTimeStr, err) + console.Errorf("Failed to parse setup time '%s' as float: %s", setupTimeStr, err) } } runTimeStr := resp.Header.Get("X-Run-Time") if runTimeStr != "" { runTime, err = strconv.ParseFloat(runTimeStr, 64) if err != nil { - console.Error("Failed to parse run time '%s' as float: %s", runTimeStr, err) + console.Errorf("Failed to parse run time '%s' as float: %s", runTimeStr, err) } } @@ -282,7 +282,7 @@ func (d *LocalDockerDeployment) writeContainerLogs(logWriter logger.Logger) { if err != nil { logWriter.WriteError(err) } else { - logWriter.WriteLogLine(logs) + logWriter.Info(logs) } } diff --git a/pkg/serving/platform.go b/pkg/serving/platform.go index 862cdc8750..853946e85b 100644 --- a/pkg/serving/platform.go +++ b/pkg/serving/platform.go @@ -10,7 +10,7 @@ import ( ) type Platform interface { - Deploy(mod *model.Model, target model.Target, logWriter logger.Logger) (Deployment, error) + Deploy(mod *model.Model, target string, logWriter logger.Logger) (Deployment, error) } type Deployment interface { diff --git a/pkg/settings/project.go b/pkg/settings/project.go index 7471acdb7d..3a8703544c 100644 --- a/pkg/settings/project.go +++ b/pkg/settings/project.go @@ -32,7 +32,7 @@ func LoadProjectSettings(projectRoot string) (*ProjectSettings, error) { } text, err := ioutil.ReadFile(settingsPath) if err != nil { - console.Warn("Failed to read %s: %s", settingsPath, err) + console.Warnf("Failed to read %s: %s", settingsPath, err) return settings, nil } diff --git a/pkg/shell/net.go b/pkg/shell/net.go index fab25b5dbe..527fe56fb5 100644 --- a/pkg/shell/net.go +++ b/pkg/shell/net.go @@ -37,7 +37,7 @@ func WaitForPort(port int, timeout time.Duration) error { func WaitForHTTPOK(url string, timeout time.Duration) error { start := time.Now() - console.Debug("Waiting for %s to become accessible", url) + console.Debugf("Waiting for %s to become accessible", url) for { now := time.Now() if now.Sub(start) > timeout { @@ -52,7 +52,7 @@ func WaitForHTTPOK(url string, timeout time.Duration) error { if resp.StatusCode != http.StatusOK { continue } - console.Debug("Got successful response from %s", url) + console.Debugf("Got successful response from %s", url) return nil } } diff --git a/pkg/storage/local.go b/pkg/storage/local.go index 2fac268354..7c1046b3b5 100644 --- a/pkg/storage/local.go +++ b/pkg/storage/local.go @@ -48,7 +48,7 @@ func (s *LocalStorage) Upload(user string, name string, id string, reader io.Rea return fmt.Errorf("Failed to create directory %s: %w", dir, err) } } - console.Debug("Saving to %s", path) + console.Debugf("Saving to %s", path) file, err := os.Create(path) if err != nil { return fmt.Errorf("Failed to create %s: %w", path, err)