Skip to content

Commit 05deb64

Browse files
Add local image flag for fast context loading (#2236)
* Add local image flag for fast context loading * Add a new experimental flag —x-localimage for use with —x-fastpush * When activated, the flag uses the .cog tmp directory as an rsync for the source code without weights * It separates the different build steps into different build contexts * On run/predict/train/serve it loads the weights in as volume mounts * This allows it to not reload the context each time a small file is changed for weights * Fix no tmpMount usage * Potential fix for code scanning alert no. 24: Potentially unsafe quoting Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Will Sackfield <[email protected]> * Fix lint * Use . in non local image for copy * Add back weights copy exclusion from non local images * Send standard build directory if not local image * Fix calculating rel dir on relative dir * Build then predict in int test * Add fast and local image to predict test * Use the image name from the config in predict * Only if we use build fast * Fix cog predict build on fast build * Feed docker image in directly * Use projectDir to fill in weights path * Put debug in the right position * Remove -t in cog predict * Capture output of test and check return code * Use absolute path for weights manifest * Use hard links rather than file copies * Mirror the local directories permissions * Follow symlink to target * Make sure directory permissions are aligned * Fix fast push * Synchronise directories between different golang modules * Use MkdirAll * Use a set for weight path check --------- Signed-off-by: Will Sackfield <[email protected]> Signed-off-by: Will Sackfield <[email protected]> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
1 parent 9636afe commit 05deb64

30 files changed

+545
-121
lines changed

pkg/cli/baseimage.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99

1010
"github.com/replicate/cog/pkg/config"
1111
"github.com/replicate/cog/pkg/docker"
12+
"github.com/replicate/cog/pkg/dockercontext"
1213
"github.com/replicate/cog/pkg/dockerfile"
1314
"github.com/replicate/cog/pkg/global"
1415
"github.com/replicate/cog/pkg/update"
@@ -122,7 +123,7 @@ func newBaseImageBuildCommand() *cobra.Command {
122123
}
123124
baseImageName := dockerfile.BaseImageName(baseImageCUDAVersion, baseImagePythonVersion, baseImageTorchVersion)
124125

125-
err = docker.Build(cwd, dockerfileContents, baseImageName, []string{}, buildNoCache, buildProgressOutput, config.BuildSourceEpochTimestamp)
126+
err = docker.Build(cwd, dockerfileContents, baseImageName, []string{}, buildNoCache, buildProgressOutput, config.BuildSourceEpochTimestamp, dockercontext.StandardBuildDirectory, nil)
126127
if err != nil {
127128
return err
128129
}

pkg/cli/build.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ var buildUseCogBaseImage bool
2525
var buildStrip bool
2626
var buildPrecompile bool
2727
var buildFast bool
28+
var buildLocalImage bool
2829

2930
const useCogBaseImageFlagKey = "use-cog-base-image"
3031

@@ -48,6 +49,7 @@ func newBuildCommand() *cobra.Command {
4849
addStripFlag(cmd)
4950
addPrecompileFlag(cmd)
5051
addFastFlag(cmd)
52+
addLocalImage(cmd)
5153
cmd.Flags().StringVarP(&buildTag, "tag", "t", "", "A name for the built image in the form 'repository:tag'")
5254
return cmd
5355
}
@@ -74,7 +76,7 @@ func buildCommand(cmd *cobra.Command, args []string) error {
7476
return err
7577
}
7678

77-
if err := image.Build(cfg, projectDir, imageName, buildSecrets, buildNoCache, buildSeparateWeights, buildUseCudaBaseImage, buildProgressOutput, buildSchemaFile, buildDockerfileFile, DetermineUseCogBaseImage(cmd), buildStrip, buildPrecompile, buildFast, nil); err != nil {
79+
if err := image.Build(cfg, projectDir, imageName, buildSecrets, buildNoCache, buildSeparateWeights, buildUseCudaBaseImage, buildProgressOutput, buildSchemaFile, buildDockerfileFile, DetermineUseCogBaseImage(cmd), buildStrip, buildPrecompile, buildFast, nil, buildLocalImage); err != nil {
7880
return err
7981
}
8082

@@ -147,6 +149,12 @@ func addFastFlag(cmd *cobra.Command) {
147149
_ = cmd.Flags().MarkHidden(fastFlag)
148150
}
149151

152+
func addLocalImage(cmd *cobra.Command) {
153+
const localImage = "x-localimage"
154+
cmd.Flags().BoolVar(&buildLocalImage, localImage, false, "Whether to use the experimental local image features")
155+
_ = cmd.Flags().MarkHidden(localImage)
156+
}
157+
150158
func checkMutuallyExclusiveFlags(cmd *cobra.Command, args []string) error {
151159
flags := []string{useCogBaseImageFlagKey, "use-cuda-base-image", "dockerfile"}
152160
var flagsSet []string

pkg/cli/debug.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ func newDebugCommand() *cobra.Command {
2727
addDockerfileFlag(cmd)
2828
addUseCogBaseImageFlag(cmd)
2929
addBuildTimestampFlag(cmd)
30+
addFastFlag(cmd)
31+
addLocalImage(cmd)
3032
cmd.Flags().StringVarP(&imageName, "image-name", "", "", "The image name to use for the generated Dockerfile")
3133

3234
return cmd
@@ -39,7 +41,7 @@ func cmdDockerfile(cmd *cobra.Command, args []string) error {
3941
}
4042

4143
command := docker.NewDockerCommand()
42-
generator, err := dockerfile.NewGenerator(cfg, projectDir, false, command)
44+
generator, err := dockerfile.NewGenerator(cfg, projectDir, buildFast, command, buildLocalImage)
4345
if err != nil {
4446
return fmt.Errorf("Error creating Dockerfile generator: %w", err)
4547
}

pkg/cli/predict.go

+28-15
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ the prediction on that.`,
5656
addGpusFlag(cmd)
5757
addSetupTimeoutFlag(cmd)
5858
addFastFlag(cmd)
59+
addLocalImage(cmd)
5960

6061
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 [email protected]")
6162
cmd.Flags().StringVarP(&outPath, "output", "o", "", "Output path")
@@ -76,22 +77,27 @@ func cmdPredict(cmd *cobra.Command, args []string) error {
7677
if err != nil {
7778
return err
7879
}
80+
7981
if cfg.Build.Fast {
8082
buildFast = cfg.Build.Fast
8183
}
8284

83-
if imageName, err = image.BuildBase(cfg, projectDir, buildUseCudaBaseImage, DetermineUseCogBaseImage(cmd), buildProgressOutput); err != nil {
84-
return err
85-
}
85+
if buildFast {
86+
imageName = config.DockerImageName(projectDir)
87+
} else {
88+
if imageName, err = image.BuildBase(cfg, projectDir, buildUseCudaBaseImage, DetermineUseCogBaseImage(cmd), buildProgressOutput); err != nil {
89+
return err
90+
}
8691

87-
// Base image doesn't have /src in it, so mount as volume
88-
volumes = append(volumes, docker.Volume{
89-
Source: projectDir,
90-
Destination: "/src",
91-
})
92+
// Base image doesn't have /src in it, so mount as volume
93+
volumes = append(volumes, docker.Volume{
94+
Source: projectDir,
95+
Destination: "/src",
96+
})
9297

93-
if gpus == "" && cfg.Build.GPU {
94-
gpus = "all"
98+
if gpus == "" && cfg.Build.GPU {
99+
gpus = "all"
100+
}
95101
}
96102

97103
} else {
@@ -127,13 +133,17 @@ func cmdPredict(cmd *cobra.Command, args []string) error {
127133

128134
console.Info("")
129135
console.Infof("Starting Docker image %s and running setup()...", imageName)
136+
dockerCommand := docker.NewDockerCommand()
130137

131-
predictor := predict.NewPredictor(docker.RunOptions{
138+
predictor, err := predict.NewPredictor(docker.RunOptions{
132139
GPUs: gpus,
133140
Image: imageName,
134141
Volumes: volumes,
135142
Env: envFlags,
136-
}, false, buildFast)
143+
}, false, buildFast, dockerCommand)
144+
if err != nil {
145+
return err
146+
}
137147

138148
go func() {
139149
captureSignal := make(chan os.Signal, 1)
@@ -155,11 +165,14 @@ func cmdPredict(cmd *cobra.Command, args []string) error {
155165
console.Info("Missing device driver, re-trying without GPU")
156166

157167
_ = predictor.Stop()
158-
predictor = predict.NewPredictor(docker.RunOptions{
168+
predictor, err = predict.NewPredictor(docker.RunOptions{
159169
Image: imageName,
160170
Volumes: volumes,
161171
Env: envFlags,
162-
}, false, buildFast)
172+
}, false, buildFast, dockerCommand)
173+
if err != nil {
174+
return err
175+
}
163176

164177
if err := predictor.Start(os.Stderr, timeout); err != nil {
165178
return err
@@ -177,7 +190,7 @@ func cmdPredict(cmd *cobra.Command, args []string) error {
177190
}
178191
}()
179192

180-
return predictIndividualInputs(predictor, inputFlags, outPath, false)
193+
return predictIndividualInputs(*predictor, inputFlags, outPath, false)
181194
}
182195

183196
func isURI(ref *openapi3.Schema) bool {

pkg/cli/push.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ func newPushCommand() *cobra.Command {
3636
addStripFlag(cmd)
3737
addPrecompileFlag(cmd)
3838
addFastFlag(cmd)
39+
addLocalImage(cmd)
3940

4041
return cmd
4142
}
@@ -63,6 +64,10 @@ func push(cmd *cobra.Command, args []string) error {
6364
if err := docker.ManifestInspect(imageName); err != nil && strings.Contains(err.Error(), `"code":"NAME_UNKNOWN"`) {
6465
return fmt.Errorf("Unable to find Replicate existing model for %s. Go to replicate.com and create a new model before pushing.", imageName)
6566
}
67+
} else {
68+
if buildLocalImage {
69+
return fmt.Errorf("Unable to push a local image model to a non replicate host, please disable the local image flag before pushing to this host.")
70+
}
6671
}
6772

6873
annotations := map[string]string{}
@@ -76,7 +81,7 @@ func push(cmd *cobra.Command, args []string) error {
7681

7782
startBuildTime := time.Now()
7883

79-
if err := image.Build(cfg, projectDir, imageName, buildSecrets, buildNoCache, buildSeparateWeights, buildUseCudaBaseImage, buildProgressOutput, buildSchemaFile, buildDockerfileFile, DetermineUseCogBaseImage(cmd), buildStrip, buildPrecompile, buildFast, annotations); err != nil {
84+
if err := image.Build(cfg, projectDir, imageName, buildSecrets, buildNoCache, buildSeparateWeights, buildUseCudaBaseImage, buildProgressOutput, buildSchemaFile, buildDockerfileFile, DetermineUseCogBaseImage(cmd), buildStrip, buildPrecompile, buildFast, annotations, buildLocalImage); err != nil {
8085
return err
8186
}
8287

pkg/cli/run.go

+7
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ func newRunCommand() *cobra.Command {
3737
addUseCogBaseImageFlag(cmd)
3838
addGpusFlag(cmd)
3939
addFastFlag(cmd)
40+
addLocalImage(cmd)
4041

4142
flags := cmd.Flags()
4243
// Flags after first argument are considered args and passed to command
@@ -67,6 +68,8 @@ func run(cmd *cobra.Command, args []string) error {
6768
gpus = "all"
6869
}
6970

71+
dockerCommand := docker.NewDockerCommand()
72+
7073
runOptions := docker.RunOptions{
7174
Args: args,
7275
Env: envFlags,
@@ -75,6 +78,10 @@ func run(cmd *cobra.Command, args []string) error {
7578
Volumes: []docker.Volume{{Source: projectDir, Destination: "/src"}},
7679
Workdir: "/src",
7780
}
81+
runOptions, err = docker.FillInWeightsManifestVolumes(dockerCommand, runOptions)
82+
if err != nil {
83+
return err
84+
}
7885

7986
if util.IsAppleSiliconMac(runtime.GOOS, runtime.GOARCH) {
8087
runOptions.Platform = "linux/amd64"

pkg/cli/serve.go

+5
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ func cmdServe(cmd *cobra.Command, arg []string) error {
6969
"--await-explicit-shutdown", "true",
7070
}
7171

72+
dockerCommand := docker.NewDockerCommand()
7273
runOptions := docker.RunOptions{
7374
Args: args,
7475
Env: envFlags,
@@ -77,6 +78,10 @@ func cmdServe(cmd *cobra.Command, arg []string) error {
7778
Volumes: []docker.Volume{{Source: projectDir, Destination: "/src"}},
7879
Workdir: "/src",
7980
}
81+
runOptions, err = docker.FillInWeightsManifestVolumes(dockerCommand, runOptions)
82+
if err != nil {
83+
return err
84+
}
8085

8186
if util.IsAppleSiliconMac(runtime.GOOS, runtime.GOARCH) {
8287
runOptions.Platform = "linux/amd64"

pkg/cli/train.go

+12-7
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,14 @@ func cmdTrain(cmd *cobra.Command, args []string) error {
5656
volumes := []docker.Volume{}
5757
gpus := gpusFlag
5858

59+
cfg, projectDir, err := config.GetConfig(projectDirFlag)
60+
if err != nil {
61+
return err
62+
}
63+
5964
if len(args) == 0 {
6065
// Build image
6166

62-
cfg, projectDir, err := config.GetConfig(projectDirFlag)
63-
if err != nil {
64-
return err
65-
}
6667
if cfg.Build.Fast {
6768
buildFast = cfg.Build.Fast
6869
}
@@ -108,14 +109,18 @@ func cmdTrain(cmd *cobra.Command, args []string) error {
108109

109110
console.Info("")
110111
console.Infof("Starting Docker image %s...", imageName)
112+
dockerCommand := docker.NewDockerCommand()
111113

112-
predictor := predict.NewPredictor(docker.RunOptions{
114+
predictor, err := predict.NewPredictor(docker.RunOptions{
113115
GPUs: gpus,
114116
Image: imageName,
115117
Volumes: volumes,
116118
Env: trainEnvFlags,
117119
Args: []string{"python", "-m", "cog.server.http", "--x-mode", "train"},
118-
}, true, buildFast)
120+
}, true, buildFast, dockerCommand)
121+
if err != nil {
122+
return err
123+
}
119124

120125
go func() {
121126
captureSignal := make(chan os.Signal, 1)
@@ -141,5 +146,5 @@ func cmdTrain(cmd *cobra.Command, args []string) error {
141146
}
142147
}()
143148

144-
return predictIndividualInputs(predictor, trainInputFlags, trainOutPath, true)
149+
return predictIndividualInputs(*predictor, trainInputFlags, trainOutPath, true)
145150
}

pkg/docker/build.go

+6-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313
"github.com/replicate/cog/pkg/util/console"
1414
)
1515

16-
func Build(dir, dockerfileContents, imageName string, secrets []string, noCache bool, progressOutput string, epoch int64) error {
16+
func Build(dir, dockerfileContents, imageName string, secrets []string, noCache bool, progressOutput string, epoch int64, contextDir string, buildContexts map[string]string) error {
1717
var args []string
1818

1919
args = append(args, "buildx", "build")
@@ -52,11 +52,15 @@ func Build(dir, dockerfileContents, imageName string, secrets []string, noCache
5252
args = append(args, "--cache-to", "type=inline")
5353
}
5454

55+
for name, dir := range buildContexts {
56+
args = append(args, "--build-context", name+"="+dir)
57+
}
58+
5559
args = append(args,
5660
"--file", "-",
5761
"--tag", imageName,
5862
"--progress", progressOutput,
59-
".",
63+
contextDir,
6064
)
6165

6266
cmd := exec.Command("docker", args...)

pkg/docker/command/manifest.go

+1
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@ const R8PythonVersionEnvVarName = "R8_PYTHON_VERSION"
2020
var CogConfigLabelKey = global.LabelNamespace + "config"
2121
var CogVersionLabelKey = global.LabelNamespace + "version"
2222
var CogOpenAPISchemaLabelKey = global.LabelNamespace + "openapi_schema"
23+
var CogWeightsManifestLabelKey = global.LabelNamespace + "r8_weights_manifest"

pkg/docker/fast_push.go

+6-4
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,16 @@ import (
1313
"github.com/vbauerster/mpb/v8"
1414

1515
"github.com/replicate/cog/pkg/docker/command"
16+
"github.com/replicate/cog/pkg/dockercontext"
1617
"github.com/replicate/cog/pkg/monobeam"
1718
"github.com/replicate/cog/pkg/requirements"
1819
"github.com/replicate/cog/pkg/util"
1920
"github.com/replicate/cog/pkg/web"
2021
"github.com/replicate/cog/pkg/weights"
2122
)
2223

24+
var TarballsDir = filepath.Join(dockercontext.CogBuildArtifactsFolder, "tarballs")
25+
2326
const weightsObjectType = "weights"
2427
const filesObjectType = "files"
2528
const requirementsTarFile = "requirements.tar.zst"
@@ -46,8 +49,7 @@ func FastPush(ctx context.Context, image string, projectDir string, command comm
4649
// Create a list of files to upload.
4750
files := []web.File{}
4851

49-
tmpAptDir := filepath.Join(projectDir, ".cog", "tmp", "apt")
50-
aptTarFile, err := CurrentAptTarball(tmpAptDir)
52+
aptTarFile, err := CurrentAptTarball(dockercontext.CogTempDir(projectDir, dockercontext.AptBuildDir))
5153
if err != nil {
5254
return fmt.Errorf("current apt tarball error: %w", err)
5355
}
@@ -67,15 +69,15 @@ func FastPush(ctx context.Context, image string, projectDir string, command comm
6769
})
6870
}
6971

70-
tmpRequirementsDir := filepath.Join(projectDir, ".cog", "tmp", "requirements")
72+
tmpRequirementsDir := dockercontext.CogTempDir(projectDir, dockercontext.RequirementsBuildDir)
7173
requirementsFile, err := requirements.CurrentRequirements(tmpRequirementsDir)
7274
if err != nil {
7375
return err
7476
}
7577

7678
// Temp directory for tarballs extracted from the image
7779
// Separate from other temp directories so that they don't cause cache invalidation
78-
tmpTarballsDir := filepath.Join(projectDir, ".cog", "tmp", "tarballs")
80+
tmpTarballsDir := filepath.Join(projectDir, TarballsDir)
7981
// Upload python packages.
8082
if requirementsFile != "" {
8183
pythonTar, err := createPythonPackagesTarFile(image, tmpTarballsDir, command)

0 commit comments

Comments
 (0)