Skip to content

Commit 362c7f6

Browse files
authored
Merge pull request #1003 from mat007/command-modifier
Add an optional CommandModifier for backend runner processes
2 parents 71f7c82 + 526aa97 commit 362c7f6

9 files changed

Lines changed: 66 additions & 15 deletions

File tree

pkg/inference/backends/diffusers/diffusers.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,13 @@ type diffusers struct {
5656
installDir string
5757
// registryMirrors is the list of registry mirrors to try before registry-1.docker.io.
5858
registryMirrors []string
59+
// commandModifier, if non-nil, is applied to the server process before it starts.
60+
commandModifier func(*exec.Cmd)
5961
}
6062

6163
// New creates a new diffusers-based backend for image generation.
6264
// customPythonPath is an optional path to a custom python3 binary; if empty, the default installation is used.
63-
func New(log logging.Logger, modelManager *models.Manager, serverLog logging.Logger, conf *Config, customPythonPath string, registryMirrors []string) (inference.Backend, error) {
65+
func New(log logging.Logger, modelManager *models.Manager, serverLog logging.Logger, conf *Config, customPythonPath string, registryMirrors []string, commandModifier func(*exec.Cmd)) (inference.Backend, error) {
6466
// If no config is provided, use the default configuration
6567
if conf == nil {
6668
conf = NewDefaultConfig()
@@ -81,6 +83,7 @@ func New(log logging.Logger, modelManager *models.Manager, serverLog logging.Log
8183
customPythonPath: customPythonPath,
8284
installDir: installDir,
8385
registryMirrors: registryMirrors,
86+
commandModifier: commandModifier,
8487
}, nil
8588
}
8689

@@ -265,6 +268,7 @@ func (d *diffusers) Run(ctx context.Context, socket, model string, modelRef stri
265268
Logger: d.log,
266269
ServerLogWriter: logging.NewWriter(d.serverLog),
267270
ErrorTransformer: ExtractPythonError,
271+
CommandModifier: d.commandModifier,
268272
})
269273
}
270274

pkg/inference/backends/llamacpp/llamacpp.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ type llamaCpp struct {
5353
gpuSupported bool
5454
// registryMirrors is the list of registry mirrors to try before registry-1.docker.io.
5555
registryMirrors []string
56+
// commandModifier, if non-nil, is applied to the server process before it starts.
57+
commandModifier func(*exec.Cmd)
5658
}
5759

5860
// New creates a new llama.cpp-based backend. installDir is the directory that
@@ -65,6 +67,7 @@ func New(
6567
installDir string,
6668
conf config.BackendConfig,
6769
registryMirrors []string,
70+
commandModifier func(*exec.Cmd),
6871
) (inference.Backend, error) {
6972
// If no config is provided, use the default configuration
7073
if conf == nil {
@@ -87,6 +90,7 @@ func New(
8790
status: inference.FormatNotInstalled(""),
8891
config: conf,
8992
registryMirrors: registryMirrors,
93+
commandModifier: commandModifier,
9094
}, nil
9195
}
9296

@@ -228,6 +232,7 @@ func (l *llamaCpp) Run(ctx context.Context, socket, model string, _ string, mode
228232
Logger: l.log,
229233
ServerLogWriter: logging.NewWriter(l.serverLog),
230234
ErrorTransformer: ExtractLlamaCppError,
235+
CommandModifier: l.commandModifier,
231236
})
232237
}
233238

pkg/inference/backends/mlx/mlx.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,13 @@ type mlx struct {
3939
pythonPath string
4040
// customPythonPath is an optional custom path to the python3 binary.
4141
customPythonPath string
42+
// commandModifier, if non-nil, is applied to the server process before it starts.
43+
commandModifier func(*exec.Cmd)
4244
}
4345

4446
// New creates a new MLX-based backend.
4547
// customPythonPath is an optional path to a custom python3 binary; if empty, the default path is used.
46-
func New(log logging.Logger, modelManager *models.Manager, serverLog logging.Logger, conf *Config, customPythonPath string) (inference.Backend, error) {
48+
func New(log logging.Logger, modelManager *models.Manager, serverLog logging.Logger, conf *Config, customPythonPath string, commandModifier func(*exec.Cmd)) (inference.Backend, error) {
4749
// If no config is provided, use the default configuration
4850
if conf == nil {
4951
conf = NewDefaultMLXConfig()
@@ -56,6 +58,7 @@ func New(log logging.Logger, modelManager *models.Manager, serverLog logging.Log
5658
config: conf,
5759
status: inference.FormatNotInstalled(""),
5860
customPythonPath: customPythonPath,
61+
commandModifier: commandModifier,
5962
}, nil
6063
}
6164

@@ -145,6 +148,7 @@ func (m *mlx) Run(ctx context.Context, socket, model string, modelRef string, mo
145148
Args: args,
146149
Logger: m.log,
147150
ServerLogWriter: logging.NewWriter(m.serverLog),
151+
CommandModifier: m.commandModifier,
148152
})
149153
}
150154

pkg/inference/backends/runner.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ type RunnerConfig struct {
4747
// process environment. If nil or empty, the backend inherits the parent
4848
// env as-is (an empty non-nil slice is treated the same as nil).
4949
Env []string
50+
// CommandModifier, if non-nil, is invoked on the backend command immediately
51+
// before it is started, after the internal setup (cancellation, stdio, env).
52+
// Embedders use it to customize process attributes such as credentials
53+
// (SysProcAttr), environment, or working directory. It composes with, and
54+
// runs after, the internal modifier.
55+
CommandModifier func(*exec.Cmd)
5056
}
5157

5258
// Logger interface for backend logging
@@ -113,6 +119,15 @@ func RunBackend(ctx context.Context, config RunnerConfig) error {
113119
if len(config.Env) > 0 {
114120
command.Env = append(os.Environ(), config.Env...)
115121
}
122+
if config.CommandModifier != nil {
123+
// Materialize the inherited environment first: a nil Env means
124+
// "inherit the parent", but a modifier that appends to it would
125+
// otherwise start from an empty slice and drop the parent env.
126+
if command.Env == nil {
127+
command.Env = os.Environ()
128+
}
129+
config.CommandModifier(command)
130+
}
116131
},
117132
config.SandboxPath,
118133
config.BinaryPath,

pkg/inference/backends/sglang/sglang.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,13 @@ type sglang struct {
4747
pythonPath string
4848
// customPythonPath is an optional custom path to the python3 binary.
4949
customPythonPath string
50+
// commandModifier, if non-nil, is applied to the server process before it starts.
51+
commandModifier func(*exec.Cmd)
5052
}
5153

5254
// New creates a new SGLang-based backend.
5355
// customPythonPath is an optional path to a custom python3 binary; if empty, the default path is used.
54-
func New(log logging.Logger, modelManager *models.Manager, serverLog logging.Logger, conf *Config, customPythonPath string) (inference.Backend, error) {
56+
func New(log logging.Logger, modelManager *models.Manager, serverLog logging.Logger, conf *Config, customPythonPath string, commandModifier func(*exec.Cmd)) (inference.Backend, error) {
5557
// If no config is provided, use the default configuration
5658
if conf == nil {
5759
conf = NewDefaultSGLangConfig()
@@ -64,6 +66,7 @@ func New(log logging.Logger, modelManager *models.Manager, serverLog logging.Log
6466
config: conf,
6567
status: inference.FormatNotInstalled(""),
6668
customPythonPath: customPythonPath,
69+
commandModifier: commandModifier,
6770
}, nil
6871
}
6972

@@ -174,6 +177,7 @@ func (s *sglang) Run(ctx context.Context, socket, model string, modelRef string,
174177
Args: args,
175178
Logger: s.log,
176179
ServerLogWriter: logging.NewWriter(s.serverLog),
180+
CommandModifier: s.commandModifier,
177181
})
178182
}
179183

pkg/inference/backends/vllm/vllm.go

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"io/fs"
99
"net/http"
1010
"os"
11+
"os/exec"
1112
"path/filepath"
1213
"strings"
1314

@@ -45,14 +46,17 @@ type vLLM struct {
4546
customBinaryPath string
4647
// registryMirrors is the list of registry mirrors to try before registry-1.docker.io.
4748
registryMirrors []string
49+
// commandModifier, if non-nil, is applied to the server process before it starts.
50+
commandModifier func(*exec.Cmd)
4851
}
4952

5053
// Options holds the configuration for the unified vLLM backend constructor.
5154
type Options struct {
52-
Config *Config // Linux-only: extra vllm args (nil = defaults)
53-
LinuxBinaryPath string // Linux: custom vllm binary path
54-
MetalPythonPath string // macOS ARM64: custom python path
55-
RegistryMirrors []string // registry mirrors tried before registry-1.docker.io
55+
Config *Config // Linux-only: extra vllm args (nil = defaults)
56+
LinuxBinaryPath string // Linux: custom vllm binary path
57+
MetalPythonPath string // macOS ARM64: custom python path
58+
RegistryMirrors []string // registry mirrors tried before registry-1.docker.io
59+
CommandModifier func(*exec.Cmd) // applied to the server process before it starts
5660
}
5761

5862
// New creates the appropriate vLLM backend for the current platform.
@@ -61,9 +65,9 @@ type Options struct {
6165
// methods return errors.
6266
func New(log logging.Logger, modelManager *models.Manager, serverLog logging.Logger, opts Options) (inference.Backend, error) {
6367
if platform.SupportsVLLMMetal() {
64-
return newMetal(log, modelManager, serverLog, opts.MetalPythonPath, opts.RegistryMirrors)
68+
return newMetal(log, modelManager, serverLog, opts.MetalPythonPath, opts.RegistryMirrors, opts.CommandModifier)
6569
}
66-
return newLinux(log, modelManager, serverLog, opts.Config, opts.LinuxBinaryPath, opts.RegistryMirrors)
70+
return newLinux(log, modelManager, serverLog, opts.Config, opts.LinuxBinaryPath, opts.RegistryMirrors, opts.CommandModifier)
6771
}
6872

6973
// NeedsDeferredInstall reports whether vllm on the current platform
@@ -74,7 +78,7 @@ func NeedsDeferredInstall() bool {
7478

7579
// newLinux creates a new Linux vLLM-based backend.
7680
// customBinaryPath is an optional path to a custom vllm binary; if empty, the default path is used.
77-
func newLinux(log logging.Logger, modelManager *models.Manager, serverLog logging.Logger, conf *Config, customBinaryPath string, registryMirrors []string) (inference.Backend, error) {
81+
func newLinux(log logging.Logger, modelManager *models.Manager, serverLog logging.Logger, conf *Config, customBinaryPath string, registryMirrors []string, commandModifier func(*exec.Cmd)) (inference.Backend, error) {
7882
// If no config is provided, use the default configuration
7983
if conf == nil {
8084
conf = NewDefaultVLLMConfig()
@@ -88,6 +92,7 @@ func newLinux(log logging.Logger, modelManager *models.Manager, serverLog loggin
8892
status: inference.FormatNotInstalled(""),
8993
customBinaryPath: customBinaryPath,
9094
registryMirrors: registryMirrors,
95+
commandModifier: commandModifier,
9196
}, nil
9297
}
9398

@@ -189,6 +194,7 @@ func (v *vLLM) Run(ctx context.Context, socket, model string, modelRef string, m
189194
Args: args,
190195
Logger: v.log,
191196
ServerLogWriter: logging.NewWriter(v.serverLog),
197+
CommandModifier: v.commandModifier,
192198
})
193199
}
194200

pkg/inference/backends/vllm/vllm_metal.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,13 @@ type vllmMetal struct {
5353
status string
5454
// registryMirrors is the list of registry mirrors to try before registry-1.docker.io.
5555
registryMirrors []string
56+
// commandModifier, if non-nil, is applied to the server process before it starts.
57+
commandModifier func(*exec.Cmd)
5658
}
5759

5860
// newMetal creates a new vllm-metal backend.
5961
// customPythonPath is an optional path to a custom python3 binary; if empty, the default installation is used.
60-
func newMetal(log logging.Logger, modelManager *models.Manager, serverLog logging.Logger, customPythonPath string, registryMirrors []string) (inference.Backend, error) {
62+
func newMetal(log logging.Logger, modelManager *models.Manager, serverLog logging.Logger, customPythonPath string, registryMirrors []string, commandModifier func(*exec.Cmd)) (inference.Backend, error) {
6163
homeDir, err := os.UserHomeDir()
6264
if err != nil {
6365
return nil, fmt.Errorf("failed to get user home directory: %w", err)
@@ -72,6 +74,7 @@ func newMetal(log logging.Logger, modelManager *models.Manager, serverLog loggin
7274
installDir: installDir,
7375
status: inference.FormatNotInstalled(""),
7476
registryMirrors: registryMirrors,
77+
commandModifier: commandModifier,
7578
}, nil
7679
}
7780

@@ -250,6 +253,7 @@ func (v *vllmMetal) Run(ctx context.Context, socket, model string, modelRef stri
250253
Logger: v.log,
251254
ServerLogWriter: logging.NewWriter(v.serverLog),
252255
Env: []string{"VLLM_HOST_IP=127.0.0.1"},
256+
CommandModifier: v.commandModifier,
253257
})
254258
}
255259

pkg/routing/backends.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package routing
22

33
import (
4+
"os/exec"
5+
46
"github.com/docker/model-runner/pkg/inference"
57
"github.com/docker/model-runner/pkg/inference/backends/diffusers"
68
"github.com/docker/model-runner/pkg/inference/backends/llamacpp"
@@ -39,6 +41,12 @@ type BackendsConfig struct {
3941
// when pulling backend images. Populated from MODEL_RUNNER_REGISTRY_MIRRORS or
4042
// injected by Docker Desktop from daemon.json registry-mirrors.
4143
RegistryMirrors []string
44+
45+
// CommandModifier, if non-nil, is applied to every backend runner process
46+
// immediately before it starts (see backends.RunnerConfig.CommandModifier).
47+
// Embedders use it to customize process attributes such as credentials or
48+
// environment; nil leaves the process unchanged.
49+
CommandModifier func(*exec.Cmd)
4250
}
4351

4452
// DefaultBackendDefs returns BackendDef entries for the configured backends.
@@ -54,13 +62,13 @@ func DefaultBackendDefs(cfg BackendsConfig) []BackendDef {
5462

5563
defs := []BackendDef{
5664
{Name: llamacpp.Name, Deferred: llamacpp.NeedsDeferredInstall(), Init: func(mm *models.Manager) (inference.Backend, error) {
57-
return llamacpp.New(cfg.Log, mm, sl(llamacpp.Name), cfg.LlamaCppPath, cfg.LlamaCppConfig, cfg.RegistryMirrors)
65+
return llamacpp.New(cfg.Log, mm, sl(llamacpp.Name), cfg.LlamaCppPath, cfg.LlamaCppConfig, cfg.RegistryMirrors, cfg.CommandModifier)
5866
}},
5967
}
6068

6169
if cfg.IncludeMLX {
6270
defs = append(defs, BackendDef{Name: mlx.Name, Init: func(mm *models.Manager) (inference.Backend, error) {
63-
return mlx.New(cfg.Log, mm, sl(mlx.Name), nil, cfg.MLXPath)
71+
return mlx.New(cfg.Log, mm, sl(mlx.Name), nil, cfg.MLXPath, cfg.CommandModifier)
6472
}})
6573
}
6674

@@ -73,6 +81,7 @@ func DefaultBackendDefs(cfg BackendsConfig) []BackendDef {
7381
LinuxBinaryPath: cfg.VLLMPath,
7482
MetalPythonPath: cfg.VLLMMetalPath,
7583
RegistryMirrors: cfg.RegistryMirrors,
84+
CommandModifier: cfg.CommandModifier,
7685
})
7786
},
7887
})
@@ -83,7 +92,7 @@ func DefaultBackendDefs(cfg BackendsConfig) []BackendDef {
8392
Name: diffusers.Name,
8493
Deferred: true,
8594
Init: func(mm *models.Manager) (inference.Backend, error) {
86-
return diffusers.New(cfg.Log, mm, sl(diffusers.Name), nil, cfg.DiffusersPath, cfg.RegistryMirrors)
95+
return diffusers.New(cfg.Log, mm, sl(diffusers.Name), nil, cfg.DiffusersPath, cfg.RegistryMirrors, cfg.CommandModifier)
8796
},
8897
})
8998
}

pkg/server/server.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ func Run(ctx context.Context, cfg Config) error {
179179
RegistryMirrors: envconfig.RegistryMirrors(),
180180
}),
181181
routing.BackendDef{Name: sglang.Name, Init: func(mm *models.Manager) (inference.Backend, error) {
182-
return sglang.New(log, mm, log.With("component", sglang.Name), nil, sglangServerPath)
182+
return sglang.New(log, mm, log.With("component", sglang.Name), nil, sglangServerPath, nil)
183183
}},
184184
),
185185
OnBackendError: func(name string, err error) {

0 commit comments

Comments
 (0)