Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
model-distribution-tool
model-runner
model-runner.sock
docker-model
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is docker-model?

# Default MODELS_PATH in Makefile
models-store/
# Default MODELS_PATH in mdltool
Expand Down
3 changes: 3 additions & 0 deletions cmd/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ Run `./model --help` to see all commands and options.

### Common Commands
- `model install-runner` — Install the Docker Model Runner
- `model start-runner` — Start the Docker Model Runner
- `model stop-runner` — Stop the Docker Model Runner
- `model restart-runner` — Restart the Docker Model Runner
- `model run MODEL [PROMPT]` — Run a model with a prompt or enter chat mode
- `model list` — List available models
- `model package --gguf <path> --push <target>` — Package and push a model
Expand Down
197 changes: 110 additions & 87 deletions cmd/cli/commands/install-runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,108 @@ func ensureStandaloneRunnerAvailable(ctx context.Context, printer standalone.Sta
return inspectStandaloneRunner(container), nil
}

// runnerOptions holds common configuration for install/start commands
type runnerOptions struct {
port uint16
host string
gpuMode string
doNotTrack bool
pullImage bool
}

// runInstallOrStart is shared logic for install-runner and start-runner commands
func runInstallOrStart(cmd *cobra.Command, opts runnerOptions) error {
// Ensure that we're running in a supported model runner context.
engineKind := modelRunner.EngineKind()
if engineKind == types.ModelRunnerEngineKindDesktop {
// TODO: We may eventually want to auto-forward this to
// docker desktop enable model-runner, but we should first make
// sure the CLI flags match.
cmd.Println("Standalone installation not supported with Docker Desktop")
cmd.Println("Use `docker desktop enable model-runner` instead")
return nil
} else if engineKind == types.ModelRunnerEngineKindMobyManual {
cmd.Println("Standalone installation not supported with MODEL_RUNNER_HOST set")
return nil
}

port := opts.port
if port == 0 {
// Use "0" as a sentinel default flag value so it's not displayed automatically.
// The default values are written in the usage string.
// Hence, the user currently won't be able to set the port to 0 in order to get a random available port.
port = standalone.DefaultControllerPortMoby
}
// HACK: If we're in a Cloud context, then we need to use a
// different default port because it conflicts with Docker Desktop's
// default model runner host-side port. Unfortunately we can't make
// the port flag default dynamic (at least not easily) because of
// when context detection happens. So assume that a default value
// indicates that we want the Cloud default port. This is less
// problematic in Cloud since the UX there is mostly invisible.
if engineKind == types.ModelRunnerEngineKindCloud &&
port == standalone.DefaultControllerPortMoby {
port = standalone.DefaultControllerPortCloud
}

// Set the appropriate environment.
environment := "moby"
if engineKind == types.ModelRunnerEngineKindCloud {
environment = "cloud"
}

// Create a Docker client for the active context.
dockerClient, err := desktop.DockerClientForContext(dockerCLI, dockerCLI.CurrentContext())
if err != nil {
return fmt.Errorf("failed to create Docker client: %w", err)
}

// Check if an active model runner container already exists.
if ctrID, ctrName, _, err := standalone.FindControllerContainer(cmd.Context(), dockerClient); err != nil {
return err
} else if ctrID != "" {
if ctrName != "" {
cmd.Printf("Model Runner container %s (%s) is already running\n", ctrName, ctrID[:12])
} else {
cmd.Printf("Model Runner container %s is already running\n", ctrID[:12])
}
return nil
}

// Determine GPU support.
var gpu gpupkg.GPUSupport
if opts.gpuMode == "auto" {
gpu, err = gpupkg.ProbeGPUSupport(cmd.Context(), dockerClient)
if err != nil {
return fmt.Errorf("unable to probe GPU support: %w", err)
}
} else if opts.gpuMode == "cuda" {
gpu = gpupkg.GPUSupportCUDA
} else if opts.gpuMode != "none" {
return fmt.Errorf("unknown GPU specification: %q", opts.gpuMode)
}

// Ensure that we have an up-to-date copy of the image, if requested.
if opts.pullImage {
if err := standalone.EnsureControllerImage(cmd.Context(), dockerClient, gpu, cmd); err != nil {
return fmt.Errorf("unable to pull latest standalone model runner image: %w", err)
}
}

// Ensure that we have a model storage volume.
modelStorageVolume, err := standalone.EnsureModelStorageVolume(cmd.Context(), dockerClient, cmd)
if err != nil {
return fmt.Errorf("unable to initialize standalone model storage: %w", err)
}
// Create the model runner container.
if err := standalone.CreateControllerContainer(cmd.Context(), dockerClient, port, opts.host, environment, opts.doNotTrack, gpu, modelStorageVolume, cmd, engineKind); err != nil {
return fmt.Errorf("unable to initialize standalone model runner container: %w", err)
}

// Poll until we get a response from the model runner.
return waitForStandaloneRunnerAfterInstall(cmd.Context())
}

func newInstallRunner() *cobra.Command {
var port uint16
var host string
Expand All @@ -169,97 +271,18 @@ func newInstallRunner() *cobra.Command {
Use: "install-runner",
Short: "Install Docker Model Runner (Docker Engine only)",
RunE: func(cmd *cobra.Command, args []string) error {
// Ensure that we're running in a supported model runner context.
engineKind := modelRunner.EngineKind()
if engineKind == types.ModelRunnerEngineKindDesktop {
// TODO: We may eventually want to auto-forward this to
// docker desktop enable model-runner, but we should first make
// sure the CLI flags match.
cmd.Println("Standalone installation not supported with Docker Desktop")
cmd.Println("Use `docker desktop enable model-runner` instead")
return nil
} else if engineKind == types.ModelRunnerEngineKindMobyManual {
cmd.Println("Standalone installation not supported with MODEL_RUNNER_HOST set")
return nil
}

if port == 0 {
// Use "0" as a sentinel default flag value so it's not displayed automatically.
// The default values are written in the usage string.
// Hence, the user currently won't be able to set the port to 0 in order to get a random available port.
port = standalone.DefaultControllerPortMoby
}
// HACK: If we're in a Cloud context, then we need to use a
// different default port because it conflicts with Docker Desktop's
// default model runner host-side port. Unfortunately we can't make
// the port flag default dynamic (at least not easily) because of
// when context detection happens. So assume that a default value
// indicates that we want the Cloud default port. This is less
// problematic in Cloud since the UX there is mostly invisible.
if engineKind == types.ModelRunnerEngineKindCloud &&
port == standalone.DefaultControllerPortMoby {
port = standalone.DefaultControllerPortCloud
}

// Set the appropriate environment.
environment := "moby"
if engineKind == types.ModelRunnerEngineKindCloud {
environment = "cloud"
}

// Create a Docker client for the active context.
dockerClient, err := desktop.DockerClientForContext(dockerCLI, dockerCLI.CurrentContext())
if err != nil {
return fmt.Errorf("failed to create Docker client: %w", err)
}

// Check if an active model runner container already exists.
if ctrID, ctrName, _, err := standalone.FindControllerContainer(cmd.Context(), dockerClient); err != nil {
return err
} else if ctrID != "" {
if ctrName != "" {
cmd.Printf("Model Runner container %s (%s) is already running\n", ctrName, ctrID[:12])
} else {
cmd.Printf("Model Runner container %s is already running\n", ctrID[:12])
}
return nil
}

// Determine GPU support.
var gpu gpupkg.GPUSupport
if gpuMode == "auto" {
gpu, err = gpupkg.ProbeGPUSupport(cmd.Context(), dockerClient)
if err != nil {
return fmt.Errorf("unable to probe GPU support: %w", err)
}
} else if gpuMode == "cuda" {
gpu = gpupkg.GPUSupportCUDA
} else if gpuMode != "none" {
return fmt.Errorf("unknown GPU specification: %q", gpuMode)
}

// Ensure that we have an up-to-date copy of the image.
if err := standalone.EnsureControllerImage(cmd.Context(), dockerClient, gpu, cmd); err != nil {
return fmt.Errorf("unable to pull latest standalone model runner image: %w", err)
}

// Ensure that we have a model storage volume.
modelStorageVolume, err := standalone.EnsureModelStorageVolume(cmd.Context(), dockerClient, cmd)
if err != nil {
return fmt.Errorf("unable to initialize standalone model storage: %w", err)
}
// Create the model runner container.
if err := standalone.CreateControllerContainer(cmd.Context(), dockerClient, port, host, environment, doNotTrack, gpu, modelStorageVolume, cmd, engineKind); err != nil {
return fmt.Errorf("unable to initialize standalone model runner container: %w", err)
}

// Poll until we get a response from the model runner.
return waitForStandaloneRunnerAfterInstall(cmd.Context())
return runInstallOrStart(cmd, runnerOptions{
port: port,
host: host,
gpuMode: gpuMode,
doNotTrack: doNotTrack,
pullImage: true,
})
},
ValidArgsFunction: completion.NoComplete,
}
c.Flags().Uint16Var(&port, "port", 0,
"Docker container port for Docker Model Runner (default: 12434 for Docker CE, 12435 for Cloud mode)")
"Docker container port for Docker Model Runner (default: 12434 for Docker Engine, 12435 for Cloud mode)")
c.Flags().StringVar(&host, "host", "127.0.0.1", "Host address to bind Docker Model Runner")
c.Flags().StringVar(&gpuMode, "gpu", "auto", "Specify GPU support (none|auto|cuda)")
c.Flags().BoolVar(&doNotTrack, "do-not-track", false, "Do not track models usage in Docker Model Runner")
Expand Down
42 changes: 42 additions & 0 deletions cmd/cli/commands/restart-runner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package commands

import (
"github.com/docker/model-runner/cmd/cli/commands/completion"
"github.com/spf13/cobra"
)

func newRestartRunner() *cobra.Command {
var port uint16
var host string
var gpuMode string
var doNotTrack bool
c := &cobra.Command{
Use: "restart-runner",
Short: "Restart Docker Model Runner (Docker Engine only)",
RunE: func(cmd *cobra.Command, args []string) error {
// First stop the runner without removing models or images
if err := runUninstallOrStop(cmd, cleanupOptions{
models: false,
removeImages: false,
}); err != nil {
return err
}

// Then start the runner with the provided options
return runInstallOrStart(cmd, runnerOptions{
port: port,
host: host,
gpuMode: gpuMode,
doNotTrack: doNotTrack,
pullImage: false,
})
},
ValidArgsFunction: completion.NoComplete,
}
c.Flags().Uint16Var(&port, "port", 0,
"Docker container port for Docker Model Runner (default: 12434 for Docker Engine, 12435 for Cloud mode)")
c.Flags().StringVar(&host, "host", "127.0.0.1", "Host address to bind Docker Model Runner")
c.Flags().StringVar(&gpuMode, "gpu", "auto", "Specify GPU support (none|auto|cuda)")
c.Flags().BoolVar(&doNotTrack, "do-not-track", false, "Do not track models usage in Docker Model Runner")
return c
}
3 changes: 3 additions & 0 deletions cmd/cli/commands/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@ func NewRootCmd(cli *command.DockerCli) *cobra.Command {
newTagCmd(),
newInstallRunner(),
newUninstallRunner(),
newStartRunner(),
newStopRunner(),
newRestartRunner(),
newConfigureCmd(),
newPSCmd(),
newDFCmd(),
Expand Down
30 changes: 30 additions & 0 deletions cmd/cli/commands/start-runner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package commands

import (
"github.com/docker/model-runner/cmd/cli/commands/completion"
"github.com/spf13/cobra"
)

func newStartRunner() *cobra.Command {
var port uint16
var gpuMode string
var doNotTrack bool
c := &cobra.Command{
Use: "start-runner",
Short: "Start Docker Model Runner (Docker Engine only)",
RunE: func(cmd *cobra.Command, args []string) error {
return runInstallOrStart(cmd, runnerOptions{
port: port,
gpuMode: gpuMode,
doNotTrack: doNotTrack,
pullImage: false,
})
},
ValidArgsFunction: completion.NoComplete,
}
c.Flags().Uint16Var(&port, "port", 0,
"Docker container port for Docker Model Runner (default: 12434 for Docker Engine, 12435 for Cloud mode)")
c.Flags().StringVar(&gpuMode, "gpu", "auto", "Specify GPU support (none|auto|cuda)")
c.Flags().BoolVar(&doNotTrack, "do-not-track", false, "Do not track models usage in Docker Model Runner")
return c
}
23 changes: 23 additions & 0 deletions cmd/cli/commands/stop-runner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package commands

import (
"github.com/docker/model-runner/cmd/cli/commands/completion"
"github.com/spf13/cobra"
)

func newStopRunner() *cobra.Command {
var models bool
c := &cobra.Command{
Use: "stop-runner",
Short: "Stop Docker Model Runner (Docker Engine only)",
RunE: func(cmd *cobra.Command, args []string) error {
return runUninstallOrStop(cmd, cleanupOptions{
models: models,
removeImages: false,
})
},
ValidArgsFunction: completion.NoComplete,
}
c.Flags().BoolVar(&models, "models", false, "Remove model storage volume")
return c
}
Loading
Loading