Skip to content

Commit 9eaff19

Browse files
authored
add --mproj to support multimodal model packaging (#524)
* add --mproj to support multimodal model packaging * extract common validation into validateAbsolutePath
1 parent d12bfc4 commit 9eaff19

File tree

3 files changed

+67
-18
lines changed

3 files changed

+67
-18
lines changed

cmd/cli/commands/package.go

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,29 @@ import (
2424
"github.com/docker/model-runner/cmd/cli/desktop"
2525
)
2626

27+
// validateAbsolutePath validates that a path is absolute and returns the cleaned path
28+
func validateAbsolutePath(path, name string) (string, error) {
29+
if !filepath.IsAbs(path) {
30+
return "", fmt.Errorf(
31+
"%s path must be absolute.\n\n"+
32+
"See 'docker model package --help' for more information",
33+
name,
34+
)
35+
}
36+
return filepath.Clean(path), nil
37+
}
38+
2739
func newPackagedCmd() *cobra.Command {
2840
var opts packageOptions
2941

3042
c := &cobra.Command{
31-
Use: "package (--gguf <path> | --safetensors-dir <path> | --from <model>) [--license <path>...] [--context-size <tokens>] [--push] MODEL",
43+
Use: "package (--gguf <path> | --safetensors-dir <path> | --from <model>) [--license <path>...] [--mmproj <path>] [--context-size <tokens>] [--push] MODEL",
3244
Short: "Package a GGUF file, Safetensors directory, or existing model into a Docker model OCI artifact.",
33-
Long: "Package a GGUF file, Safetensors directory, or existing model into a Docker model OCI artifact, with optional licenses. The package is sent to the model-runner, unless --push is specified.\n" +
45+
Long: "Package a GGUF file, Safetensors directory, or existing model into a Docker model OCI artifact, with optional licenses and multimodal projector. The package is sent to the model-runner, unless --push is specified.\n" +
3446
"When packaging a sharded GGUF model, --gguf should point to the first shard. All shard files should be siblings and should include the index in the file name (e.g. model-00001-of-00015.gguf).\n" +
3547
"When packaging a Safetensors model, --safetensors-dir should point to a directory containing .safetensors files and config files (*.json, merges.txt). All files will be auto-discovered and config files will be packaged into a tar archive.\n" +
36-
"When packaging from an existing model using --from, you can modify properties like context size to create a variant of the original model.",
48+
"When packaging from an existing model using --from, you can modify properties like context size to create a variant of the original model.\n" +
49+
"For multimodal models, use --mmproj to include a multimodal projector file.",
3750
Args: func(cmd *cobra.Command, args []string) error {
3851
if err := requireExactArgs(1, "package", "MODEL")(cmd, args); err != nil {
3952
return err
@@ -66,13 +79,11 @@ func newPackagedCmd() *cobra.Command {
6679

6780
// Validate GGUF path if provided
6881
if opts.ggufPath != "" {
69-
if !filepath.IsAbs(opts.ggufPath) {
70-
return fmt.Errorf(
71-
"GGUF path must be absolute.\n\n" +
72-
"See 'docker model package --help' for more information",
73-
)
82+
var err error
83+
opts.ggufPath, err = validateAbsolutePath(opts.ggufPath, "GGUF")
84+
if err != nil {
85+
return err
7486
}
75-
opts.ggufPath = filepath.Clean(opts.ggufPath)
7687
}
7788

7889
// Validate safetensors directory if provided
@@ -107,13 +118,29 @@ func newPackagedCmd() *cobra.Command {
107118
}
108119

109120
for i, l := range opts.licensePaths {
110-
if !filepath.IsAbs(l) {
111-
return fmt.Errorf(
112-
"license path must be absolute.\n\n" +
113-
"See 'docker model package --help' for more information",
114-
)
121+
var err error
122+
opts.licensePaths[i], err = validateAbsolutePath(l, "license")
123+
if err != nil {
124+
return err
125+
}
126+
}
127+
128+
// Validate chat template path if provided
129+
if opts.chatTemplatePath != "" {
130+
var err error
131+
opts.chatTemplatePath, err = validateAbsolutePath(opts.chatTemplatePath, "chat template")
132+
if err != nil {
133+
return err
134+
}
135+
}
136+
137+
// Validate mmproj path if provided
138+
if opts.mmprojPath != "" {
139+
var err error
140+
opts.mmprojPath, err = validateAbsolutePath(opts.mmprojPath, "mmproj")
141+
if err != nil {
142+
return err
115143
}
116-
opts.licensePaths[i] = filepath.Clean(l)
117144
}
118145

119146
// Validate dir-tar paths are relative (not absolute)
@@ -146,6 +173,7 @@ func newPackagedCmd() *cobra.Command {
146173
c.Flags().StringVar(&opts.chatTemplatePath, "chat-template", "", "absolute path to chat template file (must be Jinja format)")
147174
c.Flags().StringArrayVarP(&opts.licensePaths, "license", "l", nil, "absolute path to a license file")
148175
c.Flags().StringArrayVar(&opts.dirTarPaths, "dir-tar", nil, "relative path to directory to package as tar (can be specified multiple times)")
176+
c.Flags().StringVar(&opts.mmprojPath, "mmproj", "", "absolute path to multimodal projector file")
149177
c.Flags().BoolVar(&opts.push, "push", false, "push to registry (if not set, the model is loaded into the Model Runner content store)")
150178
c.Flags().Uint64Var(&opts.contextSize, "context-size", 0, "context size in tokens")
151179
return c
@@ -159,6 +187,7 @@ type packageOptions struct {
159187
fromModel string
160188
licensePaths []string
161189
dirTarPaths []string
190+
mmprojPath string
162191
push bool
163192
tag string
164193
}
@@ -305,6 +334,14 @@ func packageModel(ctx context.Context, cmd *cobra.Command, client *desktop.Clien
305334
}
306335
}
307336

337+
if opts.mmprojPath != "" {
338+
cmd.PrintErrf("Adding multimodal projector file from %q\n", opts.mmprojPath)
339+
pkg, err = pkg.WithMultimodalProjector(opts.mmprojPath)
340+
if err != nil {
341+
return fmt.Errorf("add multimodal projector file: %w", err)
342+
}
343+
}
344+
308345
// Check if we can use lightweight repackaging (config-only changes from existing model)
309346
useLightweight := opts.fromModel != "" && pkg.HasOnlyConfigChanges()
310347

cmd/cli/docs/reference/docker_model_package.yaml

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ command: docker model package
22
short: |
33
Package a GGUF file, Safetensors directory, or existing model into a Docker model OCI artifact.
44
long: |-
5-
Package a GGUF file, Safetensors directory, or existing model into a Docker model OCI artifact, with optional licenses. The package is sent to the model-runner, unless --push is specified.
5+
Package a GGUF file, Safetensors directory, or existing model into a Docker model OCI artifact, with optional licenses and multimodal projector. The package is sent to the model-runner, unless --push is specified.
66
When packaging a sharded GGUF model, --gguf should point to the first shard. All shard files should be siblings and should include the index in the file name (e.g. model-00001-of-00015.gguf).
77
When packaging a Safetensors model, --safetensors-dir should point to a directory containing .safetensors files and config files (*.json, merges.txt). All files will be auto-discovered and config files will be packaged into a tar archive.
88
When packaging from an existing model using --from, you can modify properties like context size to create a variant of the original model.
9-
usage: docker model package (--gguf <path> | --safetensors-dir <path> | --from <model>) [--license <path>...] [--context-size <tokens>] [--push] MODEL
9+
For multimodal models, use --mmproj to include a multimodal projector file.
10+
usage: docker model package (--gguf <path> | --safetensors-dir <path> | --from <model>) [--license <path>...] [--mmproj <path>] [--context-size <tokens>] [--push] MODEL
1011
pname: docker model
1112
plink: docker_model.yaml
1213
options:
@@ -69,6 +70,15 @@ options:
6970
experimentalcli: false
7071
kubernetes: false
7172
swarm: false
73+
- option: mmproj
74+
value_type: string
75+
description: absolute path to multimodal projector file
76+
deprecated: false
77+
hidden: false
78+
experimental: false
79+
experimentalcli: false
80+
kubernetes: false
81+
swarm: false
7282
- option: push
7383
value_type: bool
7484
default_value: "false"

cmd/cli/docs/reference/model_package.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
# docker model package
22

33
<!---MARKER_GEN_START-->
4-
Package a GGUF file, Safetensors directory, or existing model into a Docker model OCI artifact, with optional licenses. The package is sent to the model-runner, unless --push is specified.
4+
Package a GGUF file, Safetensors directory, or existing model into a Docker model OCI artifact, with optional licenses and multimodal projector. The package is sent to the model-runner, unless --push is specified.
55
When packaging a sharded GGUF model, --gguf should point to the first shard. All shard files should be siblings and should include the index in the file name (e.g. model-00001-of-00015.gguf).
66
When packaging a Safetensors model, --safetensors-dir should point to a directory containing .safetensors files and config files (*.json, merges.txt). All files will be auto-discovered and config files will be packaged into a tar archive.
77
When packaging from an existing model using --from, you can modify properties like context size to create a variant of the original model.
8+
For multimodal models, use --mmproj to include a multimodal projector file.
89

910
### Options
1011

@@ -16,6 +17,7 @@ When packaging from an existing model using --from, you can modify properties li
1617
| `--from` | `string` | | reference to an existing model to repackage |
1718
| `--gguf` | `string` | | absolute path to gguf file |
1819
| `-l`, `--license` | `stringArray` | | absolute path to a license file |
20+
| `--mmproj` | `string` | | absolute path to multimodal projector file |
1921
| `--push` | `bool` | | push to registry (if not set, the model is loaded into the Model Runner content store) |
2022
| `--safetensors-dir` | `string` | | absolute path to directory containing safetensors files and config |
2123

0 commit comments

Comments
 (0)