From 7f9042a8263669f936aa609d58a03fbe2da9395a Mon Sep 17 00:00:00 2001 From: technillogue Date: Fri, 30 Jun 2023 17:51:15 -0400 Subject: [PATCH 01/15] custom dockerfile Signed-off-by: technillogue --- Makefile | 2 +- pkg/image/build.go | 18 +++++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index d7ab08c26f..c003a85768 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ all: cog pkg/dockerfile/embed/cog.whl: python/* python/cog/* python/cog/server/* python/cog/command/* @echo "Building Python library" - rm -rf dist + #rm -rf dist $(PYTHON) -m pip install build && $(PYTHON) -m build --wheel mkdir -p pkg/dockerfile/embed cp dist/*.whl $@ diff --git a/pkg/image/build.go b/pkg/image/build.go index b6d6408691..49859bbcf3 100644 --- a/pkg/image/build.go +++ b/pkg/image/build.go @@ -48,9 +48,21 @@ func Build(cfg *config.Config, dir, imageName string, secrets []string, noCache, return fmt.Errorf("Failed to build runner Docker image: %w", err) } } else { - dockerfileContents, err := generator.GenerateDockerfileWithoutSeparateWeights() - if err != nil { - return fmt.Errorf("Failed to generate Dockerfile: %w", err) + fmt.Println("trying existing dockerfile") + fmt.Println(dir) + // if Dockerfile exists, use that instead + var dockerfileContents string + maybeDockerfile := path.Join(dir, "Dockerfile") + contents, err := os.ReadFile(maybeDockerfile) + if err == nil { + console.Info(fmt.Sprintf("Using existing Dockerfile at %s...", path.Join(dir, "Dockerfile"))) + dockerfileContents = string(contents) + } else { + // fmt.Printf("Failed to read Dockerfile: %+v\n", err) + dockerfileContents, err = generator.GenerateDockerfileWithoutSeparateWeights() + if err != nil { + return fmt.Errorf("Failed to generate Dockerfile: %w", err) + } } if err := docker.Build(dir, dockerfileContents, imageName, secrets, noCache, progressOutput); err != nil { return fmt.Errorf("Failed to build Docker image: %w", err) From 1eea2e0d0fb4aa83c9b057cde61695f1e0116d5a Mon Sep 17 00:00:00 2001 From: technillogue Date: Fri, 30 Jun 2023 23:52:04 -0400 Subject: [PATCH 02/15] FAKE_COG_VERSION for making dev builds seem like latest version Signed-off-by: technillogue --- pkg/image/build.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/image/build.go b/pkg/image/build.go index 49859bbcf3..fc1265ca35 100644 --- a/pkg/image/build.go +++ b/pkg/image/build.go @@ -78,6 +78,11 @@ func Build(cfg *config.Config, dir, imageName string, secrets []string, noCache, if err != nil { return fmt.Errorf("Failed to convert config to JSON: %w", err) } + // useful for tricking kubernetes/version_deployment.go into not reinstalling pip + fakeCogVersion := os.Getenv("FAKE_COG_VERSION") + if fakeCogVersion != "" { + global.Version = fakeCogVersion + } // We used to set the cog_version and config labels in Dockerfile, because we didn't require running the // built image to get those. But, the escaping of JSON inside a label inside a Dockerfile was gnarly, and // doesn't seem to be a problem here, so do it here instead. From 1998705ede597116dc2bddb01ab1cbd72a923a0c Mon Sep 17 00:00:00 2001 From: technillogue Date: Thu, 6 Jul 2023 15:04:37 -0400 Subject: [PATCH 03/15] BUILD_ARG Signed-off-by: technillogue --- pkg/docker/build.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/docker/build.go b/pkg/docker/build.go index 93e7ac12a9..5b5f7a2f80 100644 --- a/pkg/docker/build.go +++ b/pkg/docker/build.go @@ -23,6 +23,11 @@ func Build(dir, dockerfile, imageName string, secrets []string, noCache bool, pr args = append(args, "--platform", "linux/amd64", "--load") } + buildArg := os.Getenv("BUILD_ARG") + if buildArg != "" { + args = append(args, fmt.Sprintf("--build-arg=%s", buildArg)) + } + for _, secret := range secrets { args = append(args, "--secret", secret) } From cd949de2bde7f7628d3ff3ec8a684e02493f4407 Mon Sep 17 00:00:00 2001 From: technillogue Date: Tue, 25 Jul 2023 15:18:46 -0400 Subject: [PATCH 04/15] remove commented out code Signed-off-by: technillogue --- pkg/image/build.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/image/build.go b/pkg/image/build.go index fc1265ca35..76319104ec 100644 --- a/pkg/image/build.go +++ b/pkg/image/build.go @@ -58,7 +58,7 @@ func Build(cfg *config.Config, dir, imageName string, secrets []string, noCache, console.Info(fmt.Sprintf("Using existing Dockerfile at %s...", path.Join(dir, "Dockerfile"))) dockerfileContents = string(contents) } else { - // fmt.Printf("Failed to read Dockerfile: %+v\n", err) + // maybe clarify that there's no dockerfile in this code path, but that might be too noisy dockerfileContents, err = generator.GenerateDockerfileWithoutSeparateWeights() if err != nil { return fmt.Errorf("Failed to generate Dockerfile: %w", err) From 7addbe1aa5bee43f26ee58e96126cd83b024681d Mon Sep 17 00:00:00 2001 From: technillogue Date: Wed, 26 Jul 2023 12:28:01 -0400 Subject: [PATCH 05/15] fmt Signed-off-by: technillogue --- pkg/image/build.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/image/build.go b/pkg/image/build.go index 76319104ec..f9e7c0e9e3 100644 --- a/pkg/image/build.go +++ b/pkg/image/build.go @@ -58,7 +58,7 @@ func Build(cfg *config.Config, dir, imageName string, secrets []string, noCache, console.Info(fmt.Sprintf("Using existing Dockerfile at %s...", path.Join(dir, "Dockerfile"))) dockerfileContents = string(contents) } else { - // maybe clarify that there's no dockerfile in this code path, but that might be too noisy + // maybe clarify that there's no dockerfile in this code path, but that might be too noisy dockerfileContents, err = generator.GenerateDockerfileWithoutSeparateWeights() if err != nil { return fmt.Errorf("Failed to generate Dockerfile: %w", err) From 45419e57ebaece4f48b3c059754034ffd0292f3c Mon Sep 17 00:00:00 2001 From: technillogue Date: Mon, 11 Sep 2023 13:39:57 -0400 Subject: [PATCH 06/15] add --force flag to ln (#1295) Signed-off-by: technillogue --- pkg/dockerfile/generator.go | 2 +- pkg/dockerfile/generator_test.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/dockerfile/generator.go b/pkg/dockerfile/generator.go index af3176d2b0..c34cfee4bb 100644 --- a/pkg/dockerfile/generator.go +++ b/pkg/dockerfile/generator.go @@ -380,7 +380,7 @@ func (g *Generator) pipInstalls() string { return strings.Join( []string{ "COPY --from=deps --link /dep /dep", - "RUN ln -s /dep/* $(pyenv prefix)/lib/python*/site-packages", + "RUN ln --force -s /dep/* $(pyenv prefix)/lib/python*/site-packages", }, "\n") } diff --git a/pkg/dockerfile/generator_test.go b/pkg/dockerfile/generator_test.go index 91e507cf17..668a3aea19 100644 --- a/pkg/dockerfile/generator_test.go +++ b/pkg/dockerfile/generator_test.go @@ -121,7 +121,7 @@ ENV DEBIAN_FRONTEND=noninteractive ENV PYTHONUNBUFFERED=1 ENV LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/lib/x86_64-linux-gnu:/usr/local/nvidia/lib64:/usr/local/nvidia/bin ` + testTini() + testInstallPython("3.8") + `COPY --from=deps --link /dep /dep -RUN ln -s /dep/* $(pyenv prefix)/lib/python*/site-packages +RUN ln --force -s /dep/* $(pyenv prefix)/lib/python*/site-packages WORKDIR /src EXPOSE 5000 CMD ["python", "-m", "cog.server.http"] @@ -215,7 +215,7 @@ ENV LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/lib/x86_64-linux-gnu:/usr/local/nvidia ` + testTini() + testInstallPython("3.8") + `RUN --mount=type=cache,target=/var/cache/apt apt-get update -qq && apt-get install -qqy ffmpeg cowsay && rm -rf /var/lib/apt/lists/* COPY --from=deps --link /dep /dep -RUN ln -s /dep/* $(pyenv prefix)/lib/python*/site-packages +RUN ln --force -s /dep/* $(pyenv prefix)/lib/python*/site-packages RUN cowsay moo WORKDIR /src EXPOSE 5000 @@ -367,7 +367,7 @@ ENV LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/lib/x86_64-linux-gnu:/usr/local/nvidia ` + testTini() + testInstallPython("3.8") + `RUN --mount=type=cache,target=/var/cache/apt apt-get update -qq && apt-get install -qqy ffmpeg cowsay && rm -rf /var/lib/apt/lists/* COPY --from=deps --link /dep /dep -RUN ln -s /dep/* $(pyenv prefix)/lib/python*/site-packages +RUN ln --force -s /dep/* $(pyenv prefix)/lib/python*/site-packages RUN cowsay moo COPY --from=weights --link /src/checkpoints /src/checkpoints COPY --from=weights --link /src/models /src/models From 04b6ce54e55368927861a4d599411a629a0555b3 Mon Sep 17 00:00:00 2001 From: technillogue Date: Mon, 11 Sep 2023 18:38:01 -0400 Subject: [PATCH 07/15] remove some unnecessary logging Signed-off-by: technillogue --- Makefile | 2 +- pkg/image/build.go | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/Makefile b/Makefile index c003a85768..d7ab08c26f 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ all: cog pkg/dockerfile/embed/cog.whl: python/* python/cog/* python/cog/server/* python/cog/command/* @echo "Building Python library" - #rm -rf dist + rm -rf dist $(PYTHON) -m pip install build && $(PYTHON) -m build --wheel mkdir -p pkg/dockerfile/embed cp dist/*.whl $@ diff --git a/pkg/image/build.go b/pkg/image/build.go index 166a13eedc..9a17f91d1e 100644 --- a/pkg/image/build.go +++ b/pkg/image/build.go @@ -70,8 +70,6 @@ func Build(cfg *config.Config, dir, imageName string, secrets []string, noCache, return fmt.Errorf("Failed to build runner Docker image: %w", err) } } else { - fmt.Println("trying existing dockerfile") - fmt.Println(dir) // if Dockerfile exists, use that instead var dockerfileContents string maybeDockerfile := path.Join(dir, "Dockerfile") @@ -80,7 +78,6 @@ func Build(cfg *config.Config, dir, imageName string, secrets []string, noCache, console.Info(fmt.Sprintf("Using existing Dockerfile at %s...", path.Join(dir, "Dockerfile"))) dockerfileContents = string(contents) } else { - // maybe clarify that there's no dockerfile in this code path, but that might be too noisy dockerfileContents, err = generator.GenerateDockerfileWithoutSeparateWeights() if err != nil { return fmt.Errorf("Failed to generate Dockerfile: %w", err) From f47c2e7cf7067c5af178e66bc6112777fb5f553e Mon Sep 17 00:00:00 2001 From: technillogue Date: Wed, 13 Sep 2023 17:50:58 -0400 Subject: [PATCH 08/15] --dockerfile flag Signed-off-by: technillogue --- pkg/cli/build.go | 7 +++++++ pkg/cli/debug.go | 1 + pkg/cli/predict.go | 1 + pkg/cli/push.go | 1 + pkg/cli/run.go | 1 + pkg/cli/train.go | 1 + 6 files changed, 12 insertions(+) diff --git a/pkg/cli/build.go b/pkg/cli/build.go index bf57295c7d..0190d395b2 100644 --- a/pkg/cli/build.go +++ b/pkg/cli/build.go @@ -17,6 +17,7 @@ var buildNoCache bool var buildProgressOutput string var buildSchemaFile string var buildUseCudaBaseImage string +var buildDockerfilePath string func newBuildCommand() *cobra.Command { cmd := &cobra.Command{ @@ -31,6 +32,7 @@ func newBuildCommand() *cobra.Command { addSeparateWeightsFlag(cmd) addSchemaFlag(cmd) addUseCudaBaseImageFlag(cmd) + addDockerfileFlag(cmd) cmd.Flags().StringVarP(&buildTag, "tag", "t", "", "A name for the built image in the form 'repository:tag'") return cmd } @@ -85,3 +87,8 @@ func addSchemaFlag(cmd *cobra.Command) { func addUseCudaBaseImageFlag(cmd *cobra.Command) { cmd.Flags().StringVar(&buildUseCudaBaseImage, "use-cuda-base-image", "auto", "Use Nvidia CUDA base image, 'true' (default) or 'false' (use python base image). False results in a smaller image but may cause problems for non-torch projects") } + +func addDockerfileFlag(cmd *cobra.Command) { + cmd.Flags().StringVar(&buildDockerfilePath, "dockerfile", nil, "Path to a Dockerfile. If the flag is passed but no value is provided, defaults to the Dockerfile in the working directory.") + cmd.Flags().Lookup("dockerfile").NoOptDefVal = "Dockerfile" +} diff --git a/pkg/cli/debug.go b/pkg/cli/debug.go index ac3a8e9555..9d2e3b7a95 100644 --- a/pkg/cli/debug.go +++ b/pkg/cli/debug.go @@ -23,6 +23,7 @@ func newDebugCommand() *cobra.Command { addSeparateWeightsFlag(cmd) addUseCudaBaseImageFlag(cmd) + addDockerfileFlag(cmd) cmd.Flags().StringVarP(&imageName, "image-name", "", "", "The image name to use for the generated Dockerfile") return cmd diff --git a/pkg/cli/predict.go b/pkg/cli/predict.go index 519930f079..f6c314f300 100644 --- a/pkg/cli/predict.go +++ b/pkg/cli/predict.go @@ -47,6 +47,7 @@ the prediction on that.`, addUseCudaBaseImageFlag(cmd) addBuildProgressOutputFlag(cmd) + addDockerfileFlag(cmd) cmd.Flags().StringArrayVarP(&inputFlags, "input", "i", []string{}, "Inputs, in the form name=value. if value is prefixed with @, then it is read from a file on disk. E.g. -i path=@image.jpg") cmd.Flags().StringVarP(&outPath, "output", "o", "", "Output path") diff --git a/pkg/cli/push.go b/pkg/cli/push.go index b090ab7ce3..ced1e11fba 100644 --- a/pkg/cli/push.go +++ b/pkg/cli/push.go @@ -27,6 +27,7 @@ func newPushCommand() *cobra.Command { addSeparateWeightsFlag(cmd) addSchemaFlag(cmd) addUseCudaBaseImageFlag(cmd) + addDockerfileFlag(cmd) addBuildProgressOutputFlag(cmd) return cmd diff --git a/pkg/cli/run.go b/pkg/cli/run.go index b15e36c6d2..475566d630 100644 --- a/pkg/cli/run.go +++ b/pkg/cli/run.go @@ -24,6 +24,7 @@ func newRunCommand() *cobra.Command { Args: cobra.MinimumNArgs(1), } addBuildProgressOutputFlag(cmd) + addDockerfileFlag(cmd) addUseCudaBaseImageFlag(cmd) flags := cmd.Flags() diff --git a/pkg/cli/train.go b/pkg/cli/train.go index e1f92ccc5c..dba09ea2a8 100644 --- a/pkg/cli/train.go +++ b/pkg/cli/train.go @@ -31,6 +31,7 @@ It will build the model in the current directory and train it.`, } addBuildProgressOutputFlag(cmd) + addDockerfileFlag(cmd) addUseCudaBaseImageFlag(cmd) cmd.Flags().StringArrayVarP(&trainInputFlags, "input", "i", []string{}, "Inputs, in the form name=value. if value is prefixed with @, then it is read from a file on disk. E.g. -i path=@image.jpg") From a4c163aee72ff78f343228196c78de22a550c18c Mon Sep 17 00:00:00 2001 From: technillogue Date: Thu, 14 Sep 2023 18:16:54 -0400 Subject: [PATCH 09/15] use empty string instead of nil Signed-off-by: technillogue --- pkg/cli/build.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cli/build.go b/pkg/cli/build.go index 0190d395b2..9b366d30a4 100644 --- a/pkg/cli/build.go +++ b/pkg/cli/build.go @@ -89,6 +89,6 @@ func addUseCudaBaseImageFlag(cmd *cobra.Command) { } func addDockerfileFlag(cmd *cobra.Command) { - cmd.Flags().StringVar(&buildDockerfilePath, "dockerfile", nil, "Path to a Dockerfile. If the flag is passed but no value is provided, defaults to the Dockerfile in the working directory.") + cmd.Flags().StringVar(&buildDockerfilePath, "dockerfile", "", "Path to a Dockerfile. If the flag is passed but no value is provided, defaults to the Dockerfile in the working directory.") cmd.Flags().Lookup("dockerfile").NoOptDefVal = "Dockerfile" } From 1b2528e296cea296edb08772a7c384f1d03bff3a Mon Sep 17 00:00:00 2001 From: technillogue Date: Thu, 21 Sep 2023 14:54:59 -0700 Subject: [PATCH 10/15] use configured dockerfile path Signed-off-by: technillogue --- pkg/image/build.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pkg/image/build.go b/pkg/image/build.go index 9a17f91d1e..6803a73042 100644 --- a/pkg/image/build.go +++ b/pkg/image/build.go @@ -72,12 +72,15 @@ func Build(cfg *config.Config, dir, imageName string, secrets []string, noCache, } else { // if Dockerfile exists, use that instead var dockerfileContents string - maybeDockerfile := path.Join(dir, "Dockerfile") + maybeDockerfile := path.Join(dir, buildDockerfilePath) contents, err := os.ReadFile(maybeDockerfile) if err == nil { - console.Info(fmt.Sprintf("Using existing Dockerfile at %s...", path.Join(dir, "Dockerfile"))) + console.Info(fmt.Sprintf("Using existing Dockerfile at %s...", maybeDockerfile)) dockerfileContents = string(contents) } else { + if buildDockerfilePath == "" { + console.Info(fmt.Sprintf("Couldn't find provided Dockerfile at %s, generating a Dockerfile with cog")) + } dockerfileContents, err = generator.GenerateDockerfileWithoutSeparateWeights() if err != nil { return fmt.Errorf("Failed to generate Dockerfile: %w", err) From 2cde154f9e5e186a9be547a683dfa0b17a5be382 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Fri, 22 Sep 2023 06:07:40 -0700 Subject: [PATCH 11/15] Fix logic for using custom Dockerfile Signed-off-by: Mattt Zmuda --- pkg/cli/build.go | 2 +- pkg/cli/push.go | 2 +- pkg/image/build.go | 100 ++++++++++++++++++++++----------------------- 3 files changed, 51 insertions(+), 53 deletions(-) diff --git a/pkg/cli/build.go b/pkg/cli/build.go index 9b366d30a4..ae5b4e6146 100644 --- a/pkg/cli/build.go +++ b/pkg/cli/build.go @@ -51,7 +51,7 @@ func buildCommand(cmd *cobra.Command, args []string) error { imageName = config.DockerImageName(projectDir) } - if err := image.Build(cfg, projectDir, imageName, buildSecrets, buildNoCache, buildSeparateWeights, buildUseCudaBaseImage, buildProgressOutput, buildSchemaFile); err != nil { + if err := image.Build(cfg, projectDir, imageName, buildSecrets, buildNoCache, buildSeparateWeights, buildUseCudaBaseImage, buildProgressOutput, buildSchemaFile, buildDockerfilePath); err != nil { return err } diff --git a/pkg/cli/push.go b/pkg/cli/push.go index ced1e11fba..edc840733d 100644 --- a/pkg/cli/push.go +++ b/pkg/cli/push.go @@ -48,7 +48,7 @@ func push(cmd *cobra.Command, args []string) error { return fmt.Errorf("To push images, you must either set the 'image' option in cog.yaml or pass an image name as an argument. For example, 'cog push registry.hooli.corp/hotdog-detector'") } - if err := image.Build(cfg, projectDir, imageName, buildSecrets, buildNoCache, buildSeparateWeights, buildUseCudaBaseImage, buildProgressOutput, buildSchemaFile); err != nil { + if err := image.Build(cfg, projectDir, imageName, buildSecrets, buildNoCache, buildSeparateWeights, buildUseCudaBaseImage, buildProgressOutput, buildSchemaFile, buildDockerfilePath); err != nil { return err } diff --git a/pkg/image/build.go b/pkg/image/build.go index 6803a73042..81e7a2d528 100644 --- a/pkg/image/build.go +++ b/pkg/image/build.go @@ -24,70 +24,68 @@ const weightsManifestPath = ".cog/cache/weights_manifest.json" // Build a Cog model from a config // // This is separated out from docker.Build(), so that can be as close as possible to the behavior of 'docker build'. -func Build(cfg *config.Config, dir, imageName string, secrets []string, noCache, separateWeights bool, useCudaBaseImage string, progressOutput string, schemaFile string) error { +func Build(cfg *config.Config, dir, imageName string, secrets []string, noCache, separateWeights bool, useCudaBaseImage string, progressOutput string, schemaFile string, dockerfileFile string) error { console.Infof("Building Docker image from environment in cog.yaml as %s...", imageName) - generator, err := dockerfile.NewGenerator(cfg, dir) - if err != nil { - return fmt.Errorf("Error creating Dockerfile generator: %w", err) - } - defer func() { - if err := generator.Cleanup(); err != nil { - console.Warnf("Error cleaning up Dockerfile generator: %s", err) - } - }() - generator.SetUseCudaBaseImage(useCudaBaseImage) - - if separateWeights { - weightsDockerfile, runnerDockerfile, dockerignore, err := generator.Generate(imageName) + if dockerfileFile != "" { + dockerfileContents, err := os.ReadFile(dockerfileFile) if err != nil { - return fmt.Errorf("Failed to generate Dockerfile: %w", err) + return fmt.Errorf("Failed to read Dockerfile at %s: %w", dockerfileFile, err) } - - if err := backupDockerignore(); err != nil { - return fmt.Errorf("Failed to backup .dockerignore file: %w", err) + if err := docker.Build(dir, string(dockerfileContents), imageName, secrets, noCache, progressOutput); err != nil { + return fmt.Errorf("Failed to build Docker image: %w", err) } - - weightsManifest, err := generator.GenerateWeightsManifest() + } else { + generator, err := dockerfile.NewGenerator(cfg, dir) if err != nil { - return fmt.Errorf("Failed to generate weights manifest: %w", err) + return fmt.Errorf("Error creating Dockerfile generator: %w", err) } - cachedManifest, _ := weights.LoadManifest(weightsManifestPath) - changed := cachedManifest == nil || !weightsManifest.Equal(cachedManifest) - if changed { - if err := buildWeightsImage(dir, weightsDockerfile, imageName+"-weights", secrets, noCache, progressOutput); err != nil { - return fmt.Errorf("Failed to build model weights Docker image: %w", err) + defer func() { + if err := generator.Cleanup(); err != nil { + console.Warnf("Error cleaning up Dockerfile generator: %s", err) } - err := weightsManifest.Save(weightsManifestPath) + }() + generator.SetUseCudaBaseImage(useCudaBaseImage) + + if separateWeights { + weightsDockerfile, runnerDockerfile, dockerignore, err := generator.Generate(imageName) if err != nil { - return fmt.Errorf("Failed to save weights hash: %w", err) + return fmt.Errorf("Failed to generate Dockerfile: %w", err) } - } else { - console.Info("Weights unchanged, skip rebuilding and use cached image...") - } - if err := buildRunnerImage(dir, runnerDockerfile, dockerignore, imageName, secrets, noCache, progressOutput); err != nil { - return fmt.Errorf("Failed to build runner Docker image: %w", err) - } - } else { - // if Dockerfile exists, use that instead - var dockerfileContents string - maybeDockerfile := path.Join(dir, buildDockerfilePath) - contents, err := os.ReadFile(maybeDockerfile) - if err == nil { - console.Info(fmt.Sprintf("Using existing Dockerfile at %s...", maybeDockerfile)) - dockerfileContents = string(contents) - } else { - if buildDockerfilePath == "" { - console.Info(fmt.Sprintf("Couldn't find provided Dockerfile at %s, generating a Dockerfile with cog")) + if err := backupDockerignore(); err != nil { + return fmt.Errorf("Failed to backup .dockerignore file: %w", err) } - dockerfileContents, err = generator.GenerateDockerfileWithoutSeparateWeights() + + weightsManifest, err := generator.GenerateWeightsManifest() + if err != nil { + return fmt.Errorf("Failed to generate weights manifest: %w", err) + } + cachedManifest, _ := weights.LoadManifest(weightsManifestPath) + changed := cachedManifest == nil || !weightsManifest.Equal(cachedManifest) + if changed { + if err := buildWeightsImage(dir, weightsDockerfile, imageName+"-weights", secrets, noCache, progressOutput); err != nil { + return fmt.Errorf("Failed to build model weights Docker image: %w", err) + } + err := weightsManifest.Save(weightsManifestPath) + if err != nil { + return fmt.Errorf("Failed to save weights hash: %w", err) + } + } else { + console.Info("Weights unchanged, skip rebuilding and use cached image...") + } + + if err := buildRunnerImage(dir, runnerDockerfile, dockerignore, imageName, secrets, noCache, progressOutput); err != nil { + return fmt.Errorf("Failed to build runner Docker image: %w", err) + } + } else { + dockerfileContents, err := generator.GenerateDockerfileWithoutSeparateWeights() if err != nil { return fmt.Errorf("Failed to generate Dockerfile: %w", err) } - } - if err := docker.Build(dir, dockerfileContents, imageName, secrets, noCache, progressOutput); err != nil { - return fmt.Errorf("Failed to build Docker image: %w", err) + if err := docker.Build(dir, dockerfileContents, imageName, secrets, noCache, progressOutput); err != nil { + return fmt.Errorf("Failed to build Docker image: %w", err) + } } } @@ -98,7 +96,7 @@ func Build(cfg *config.Config, dir, imageName string, secrets []string, noCache, if schemaFile != "" { // We were passed a schema file, so use that - schemaJSON, err = os.ReadFile(schemaFile) + schemaJSON, err := os.ReadFile(schemaFile) if err != nil { return fmt.Errorf("Failed to read schema file: %w", err) } @@ -109,7 +107,7 @@ func Build(cfg *config.Config, dir, imageName string, secrets []string, noCache, return fmt.Errorf("Failed to parse schema file: %w", err) } } else { - schema, err = GenerateOpenAPISchema(imageName, cfg.Build.GPU) + schema, err := GenerateOpenAPISchema(imageName, cfg.Build.GPU) if err != nil { return fmt.Errorf("Failed to get type signature: %w", err) } From 64d4852bf60f01785f0bcc71dca38e208de68645 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Fri, 22 Sep 2023 06:08:05 -0700 Subject: [PATCH 12/15] Rename buildDockerfilePath to buildDockerfileFile for consistency Signed-off-by: Mattt Zmuda --- pkg/cli/build.go | 6 +++--- pkg/cli/push.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/cli/build.go b/pkg/cli/build.go index ae5b4e6146..6e63ee6713 100644 --- a/pkg/cli/build.go +++ b/pkg/cli/build.go @@ -17,7 +17,7 @@ var buildNoCache bool var buildProgressOutput string var buildSchemaFile string var buildUseCudaBaseImage string -var buildDockerfilePath string +var buildDockerfileFile string func newBuildCommand() *cobra.Command { cmd := &cobra.Command{ @@ -51,7 +51,7 @@ func buildCommand(cmd *cobra.Command, args []string) error { imageName = config.DockerImageName(projectDir) } - if err := image.Build(cfg, projectDir, imageName, buildSecrets, buildNoCache, buildSeparateWeights, buildUseCudaBaseImage, buildProgressOutput, buildSchemaFile, buildDockerfilePath); err != nil { + if err := image.Build(cfg, projectDir, imageName, buildSecrets, buildNoCache, buildSeparateWeights, buildUseCudaBaseImage, buildProgressOutput, buildSchemaFile, buildDockerfileFile); err != nil { return err } @@ -89,6 +89,6 @@ func addUseCudaBaseImageFlag(cmd *cobra.Command) { } func addDockerfileFlag(cmd *cobra.Command) { - cmd.Flags().StringVar(&buildDockerfilePath, "dockerfile", "", "Path to a Dockerfile. If the flag is passed but no value is provided, defaults to the Dockerfile in the working directory.") + cmd.Flags().StringVar(&buildDockerfileFile, "dockerfile", "", "Path to a Dockerfile. If the flag is passed but no value is provided, defaults to the Dockerfile in the working directory.") cmd.Flags().Lookup("dockerfile").NoOptDefVal = "Dockerfile" } diff --git a/pkg/cli/push.go b/pkg/cli/push.go index edc840733d..95f59127a3 100644 --- a/pkg/cli/push.go +++ b/pkg/cli/push.go @@ -48,7 +48,7 @@ func push(cmd *cobra.Command, args []string) error { return fmt.Errorf("To push images, you must either set the 'image' option in cog.yaml or pass an image name as an argument. For example, 'cog push registry.hooli.corp/hotdog-detector'") } - if err := image.Build(cfg, projectDir, imageName, buildSecrets, buildNoCache, buildSeparateWeights, buildUseCudaBaseImage, buildProgressOutput, buildSchemaFile, buildDockerfilePath); err != nil { + if err := image.Build(cfg, projectDir, imageName, buildSecrets, buildNoCache, buildSeparateWeights, buildUseCudaBaseImage, buildProgressOutput, buildSchemaFile, buildDockerfileFile); err != nil { return err } From ffe657f652444215bd30ea9b666ea1818de2dba6 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Fri, 22 Sep 2023 06:20:09 -0700 Subject: [PATCH 13/15] Remove NoOptDefVal for dockerfile flag Signed-off-by: Mattt Zmuda --- pkg/cli/build.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/cli/build.go b/pkg/cli/build.go index 6e63ee6713..cc46491ed4 100644 --- a/pkg/cli/build.go +++ b/pkg/cli/build.go @@ -90,5 +90,4 @@ func addUseCudaBaseImageFlag(cmd *cobra.Command) { func addDockerfileFlag(cmd *cobra.Command) { cmd.Flags().StringVar(&buildDockerfileFile, "dockerfile", "", "Path to a Dockerfile. If the flag is passed but no value is provided, defaults to the Dockerfile in the working directory.") - cmd.Flags().Lookup("dockerfile").NoOptDefVal = "Dockerfile" } From 83a9bddd85a6dfa4afb9fd006f82595ab84b1652 Mon Sep 17 00:00:00 2001 From: Mattt Date: Fri, 22 Sep 2023 06:29:49 -0700 Subject: [PATCH 14/15] Remove unrelated code changes Signed-off-by: Mattt Zmuda --- pkg/docker/build.go | 5 ----- pkg/image/build.go | 5 ----- 2 files changed, 10 deletions(-) diff --git a/pkg/docker/build.go b/pkg/docker/build.go index 5b5f7a2f80..93e7ac12a9 100644 --- a/pkg/docker/build.go +++ b/pkg/docker/build.go @@ -23,11 +23,6 @@ func Build(dir, dockerfile, imageName string, secrets []string, noCache bool, pr args = append(args, "--platform", "linux/amd64", "--load") } - buildArg := os.Getenv("BUILD_ARG") - if buildArg != "" { - args = append(args, fmt.Sprintf("--build-arg=%s", buildArg)) - } - for _, secret := range secrets { args = append(args, "--secret", secret) } diff --git a/pkg/image/build.go b/pkg/image/build.go index 81e7a2d528..2478bf08b7 100644 --- a/pkg/image/build.go +++ b/pkg/image/build.go @@ -141,11 +141,6 @@ func Build(cfg *config.Config, dir, imageName string, secrets []string, noCache, if err != nil { return fmt.Errorf("Failed to convert config to JSON: %w", err) } - // useful for tricking kubernetes/version_deployment.go into not reinstalling pip - fakeCogVersion := os.Getenv("FAKE_COG_VERSION") - if fakeCogVersion != "" { - global.Version = fakeCogVersion - } labels := map[string]string{ global.LabelNamespace + "version": global.Version, From b30dabfba76a1b67d8c90beec0ed270dfb53a957 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Fri, 22 Sep 2023 11:11:00 -0700 Subject: [PATCH 15/15] Fix logic for serializing and validating schema JSON Signed-off-by: Mattt Zmuda --- pkg/image/build.go | 54 +++++++--------- .../test_integration/test_build.py | 61 +++++++++++-------- 2 files changed, 56 insertions(+), 59 deletions(-) diff --git a/pkg/image/build.go b/pkg/image/build.go index 2478bf08b7..4261a45597 100644 --- a/pkg/image/build.go +++ b/pkg/image/build.go @@ -91,45 +91,37 @@ func Build(cfg *config.Config, dir, imageName string, secrets []string, noCache, console.Info("Validating model schema...") - var schema map[string]interface{} var schemaJSON []byte - if schemaFile != "" { - // We were passed a schema file, so use that - schemaJSON, err := os.ReadFile(schemaFile) + data, err := os.ReadFile(schemaFile) if err != nil { return fmt.Errorf("Failed to read schema file: %w", err) } - schema = make(map[string]interface{}) - err = json.Unmarshal(schemaJSON, &schema) - if err != nil { - return fmt.Errorf("Failed to parse schema file: %w", err) - } + schemaJSON = data } else { schema, err := GenerateOpenAPISchema(imageName, cfg.Build.GPU) if err != nil { return fmt.Errorf("Failed to get type signature: %w", err) } - schemaJSON, err = json.Marshal(schema) + data, err := json.Marshal(schema) if err != nil { return fmt.Errorf("Failed to convert type signature to JSON: %w", err) } - } - if len(schema) > 0 { - loader := openapi3.NewLoader() - loader.IsExternalRefsAllowed = true - doc, err := loader.LoadFromData(schemaJSON) - if err != nil { - return fmt.Errorf("Failed to load model schema JSON: %w", err) - } + schemaJSON = data + } - err = doc.Validate(loader.Context) - if err != nil { - return fmt.Errorf("Model schema is invalid: %w\n\n%s", err, string(schemaJSON)) - } + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + doc, err := loader.LoadFromData(schemaJSON) + if err != nil { + return fmt.Errorf("Failed to load model schema JSON: %w", err) + } + err = doc.Validate(loader.Context) + if err != nil { + return fmt.Errorf("Model schema is invalid: %w\n\n%s", err, string(schemaJSON)) } console.Info("Adding labels to image...") @@ -143,20 +135,18 @@ func Build(cfg *config.Config, dir, imageName string, secrets []string, noCache, } labels := map[string]string{ - global.LabelNamespace + "version": global.Version, - global.LabelNamespace + "config": string(bytes.TrimSpace(configJSON)), + global.LabelNamespace + "version": global.Version, + global.LabelNamespace + "config": string(bytes.TrimSpace(configJSON)), + global.LabelNamespace + "openapi_schema": string(schemaJSON), // Mark the image as having an appropriate init entrypoint. We can use this // to decide how/if to shim the image. global.LabelNamespace + "has_init": "true", - // Backwards compatibility. Remove for 1.0. - "org.cogmodel.deprecated": "The org.cogmodel labels are deprecated. Use run.cog.", - "org.cogmodel.cog_version": global.Version, - "org.cogmodel.config": string(bytes.TrimSpace(configJSON)), - } - if len(schema) > 0 { - labels[global.LabelNamespace+"openapi_schema"] = string(schemaJSON) - labels["org.cogmodel.openapi_schema"] = string(schemaJSON) + // Backwards compatibility. Remove for 1.0. + "org.cogmodel.deprecated": "The org.cogmodel labels are deprecated. Use run.cog.", + "org.cogmodel.cog_version": global.Version, + "org.cogmodel.config": string(bytes.TrimSpace(configJSON)), + "org.cogmodel.openapi_schema": string(schemaJSON), } if isGitRepo(dir) { diff --git a/test-integration/test_integration/test_build.py b/test-integration/test_integration/test_build.py index c4464ac663..dcea8ee1b0 100644 --- a/test-integration/test_integration/test_build.py +++ b/test-integration/test_integration/test_build.py @@ -8,32 +8,13 @@ def test_build_without_predictor(docker_image): project_dir = Path(__file__).parent / "fixtures/no-predictor-project" - subprocess.run( + build_process = subprocess.run( ["cog", "build", "-t", docker_image], cwd=project_dir, - check=True, - ) - assert docker_image in str( - subprocess.run(["docker", "images"], capture_output=True, check=True).stdout - ) - image = json.loads( - subprocess.run( - ["docker", "image", "inspect", docker_image], - capture_output=True, - check=True, - ).stdout + capture_output=True, ) - labels = image[0]["Config"]["Labels"] - assert len(labels["run.cog.version"]) > 0 - assert json.loads(labels["run.cog.config"]) == {"build": {"python_version": "3.8"}} - assert "run.cog.openapi_schema" not in labels - - # Deprecated. Remove for 1.0. - assert len(labels["org.cogmodel.cog_version"]) > 0 - assert json.loads(labels["org.cogmodel.config"]) == { - "build": {"python_version": "3.8"} - } - assert "org.cogmodel.openapi_schema" not in labels + assert build_process.returncode > 0 + assert "Model schema is invalid" in build_process.stderr.decode() def test_build_names_uses_image_option_in_cog_yaml(tmpdir, docker_image): @@ -42,9 +23,21 @@ def test_build_names_uses_image_option_in_cog_yaml(tmpdir, docker_image): image: {docker_image} build: python_version: 3.8 +predict: predict.py:Predictor """ f.write(cog_yaml) + with open(tmpdir / "predict.py", "w") as f: + code = """ +from cog import BasePredictor + +class Predictor(BasePredictor): + def predict(self, text: str) -> str: + return text + +""" + f.write(code) + subprocess.run( ["cog", "build"], cwd=tmpdir, @@ -111,9 +104,21 @@ def test_build_gpu_model_on_cpu(tmpdir, docker_image): build: python_version: 3.8 gpu: true +predict: predict.py:Predictor """ f.write(cog_yaml) + with open(tmpdir / "predict.py", "w") as f: + code = """ +from cog import BasePredictor + +class Predictor(BasePredictor): + def predict(self, text: str) -> str: + return text + +""" + f.write(code) + subprocess.run( ["git", "config", "--global", "user.email", "noreply@replicate.com"], cwd=tmpdir, @@ -166,9 +171,10 @@ def test_build_gpu_model_on_cpu(tmpdir, docker_image): "gpu": True, "cuda": "11.8", "cudnn": "8", - } + }, + "predict": "predict.py:Predictor", } - assert "run.cog.openapi_schema" not in labels + assert "run.cog.openapi_schema" in labels # Deprecated. Remove for 1.0. assert len(labels["org.cogmodel.cog_version"]) > 0 @@ -178,9 +184,10 @@ def test_build_gpu_model_on_cpu(tmpdir, docker_image): "gpu": True, "cuda": "11.8", "cudnn": "8", - } + }, + "predict": "predict.py:Predictor", } - assert "org.cogmodel.openapi_schema" not in labels + assert "org.cogmodel.openapi_schema" in labels assert len(labels["org.opencontainers.image.version"]) > 0 assert len(labels["org.opencontainers.image.revision"]) > 0