Skip to content

Commit 2b0b207

Browse files
author
nold
committed
feat: add environment variables support for backends in model configurations
- Add field to model configuration to pass environment variables to backend processes - Update backend options and model configuration handling - Add documentation for environment variables configuration with examples including CUDA_VISIBLE_DEVICES Assisted-by: qwen-agentworld-35b-a3b Signed-off-by: nold <nold42@pm.me>
1 parent 40e6599 commit 2b0b207

10 files changed

Lines changed: 78 additions & 5 deletions

File tree

backend/backend.proto

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,9 @@ message ModelOptions {
445445
// Unknown keys produce an error at LoadModel time.
446446
string EngineArgs = 73;
447447

448+
// EnvVars carries environment variables to be passed to the backend process.
449+
map<string, string> EnvVars = 75;
450+
448451
// Proxy carries the cloud-proxy backend's per-model configuration.
449452
// Empty for non-proxy backends.
450453
ProxyOptions Proxy = 74;

core/backend/options.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,10 @@ func ModelOptions(c config.ModelConfig, so *config.ApplicationConfig, opts ...mo
187187
defOpts = append(defOpts, model.WithModelSizeBytes(sizeBytes))
188188
}
189189

190+
if c.Environment != nil && len(c.Environment) > 0 {
191+
defOpts = append(defOpts, model.WithEnvVars(c.Environment))
192+
}
193+
190194
return append(defOpts, opts...)
191195
}
192196

@@ -410,6 +414,14 @@ func grpcModelOpts(c config.ModelConfig, modelPath string) *pb.ModelOptions {
410414
opts.DraftModel = filepath.Join(modelPath, c.DraftModel)
411415
}
412416

417+
// Add environment variables from model configuration
418+
if c.Environment != nil && len(c.Environment) > 0 {
419+
opts.EnvVars = make(map[string]string)
420+
for k, v := range c.Environment {
421+
opts.EnvVars[k] = v
422+
}
423+
}
424+
413425
return opts
414426
}
415427

core/config/meta/registry.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,13 @@ func DefaultRegistry() map[string]FieldMetaOverride {
6666
Options: UsecaseOptions,
6767
Order: 6,
6868
},
69+
"env": {
70+
Section: "general",
71+
Label: "Environment Variables",
72+
Description: "Environment variables to be applied to the backend process",
73+
Component: "map-editor",
74+
Order: 7,
75+
},
6976

7077
// --- LLM ---
7178
"context_size": {

core/config/model_config.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,9 @@ type ModelConfig struct {
134134
Proxy ProxyConfig `yaml:"proxy,omitempty" json:"proxy,omitempty"`
135135
MITM MITMModelConfig `yaml:"mitm,omitempty" json:"mitm,omitempty"`
136136
Limits LimitsConfig `yaml:"limits,omitempty" json:"limits,omitempty"`
137+
138+
// Environment variables to set when starting the backend process
139+
Environment map[string]string `yaml:"env,omitempty" json:"env,omitempty"`
137140
}
138141

139142
// @Description Admission-control limits applied per request. The

core/config/model_config_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,20 @@ parameters:
9898
Expect(valid).To(BeTrue())
9999
Expect(err).NotTo(HaveOccurred())
100100

101+
// Test environment variables configuration
102+
envConfig := ModelConfig{
103+
Name: "env-test-model",
104+
Backend: "test-backend",
105+
Environment: map[string]string{
106+
"TEST_ENV_VAR": "test_value",
107+
"ANOTHER_VAR": "another_value",
108+
},
109+
}
110+
Expect(envConfig.Environment).To(HaveKey("TEST_ENV_VAR"))
111+
Expect(envConfig.Environment["TEST_ENV_VAR"]).To(Equal("test_value"))
112+
Expect(envConfig.Environment).To(HaveKey("ANOTHER_VAR"))
113+
Expect(envConfig.Environment["ANOTHER_VAR"]).To(Equal("another_value"))
114+
101115
tcAndChat := FLAG_TOKEN_CLASSIFY | FLAG_CHAT
102116
tcCombined := ModelConfig{
103117
Name: "ner-and-chat",

core/services/worker/supervisor.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ func (s *backendSupervisor) startBackend(backend, backendPath string) (string, e
8181
bindAddr := fmt.Sprintf("0.0.0.0:%d", port)
8282
clientAddr := fmt.Sprintf("127.0.0.1:%d", port)
8383

84-
proc, err := s.ml.StartProcess(backendPath, backend, bindAddr)
84+
proc, err := s.ml.StartProcess(backendPath, backend, bindAddr, nil)
8585
if err != nil {
8686
s.mu.Unlock()
8787
return "", fmt.Errorf("starting backend process: %w", err)

docs/content/advanced/model-configuration.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -909,6 +909,24 @@ Multiple detectors union their detections; overlapping spans resolve to the stro
909909

910910
> The earlier regex pattern tier (`pii.patterns`, the global pattern catalogue, `--pii-config`, and the `/api/pii/patterns` admin endpoints) has been removed, along with response/streaming-side redaction. Those keys now no-op with a startup warning; migrate to `pii.detectors` + a detector's `pii_detection` block.
911911

912+
## Environment Variables Configuration
913+
914+
Model configurations can specify environment variables passed to the backend process:
915+
916+
```yaml
917+
name: vllm-model
918+
backend: vllm
919+
parameters:
920+
model: my-vllm-model
921+
922+
env:
923+
VLLM_WORKER_MULTIPROC_METHOD: "spawn"
924+
VLLM_CACHE_DIR: "/tmp/vllm_cache"
925+
CUDA_VISIBLE_DEVICES: "0,1"
926+
```
927+
928+
Environment variables are appended to the system environment variables and will override any conflicting system variables with the same name.
929+
912930
## Complete Example
913931

914932
Here's a comprehensive example combining many options:

pkg/model/initializers.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ func (ml *ModelLoader) spawnGRPCModel(backend, uri string, o *Options, modelID,
119119
return nil, fmt.Errorf("failed allocating free ports: %s", err.Error())
120120
}
121121
// Make sure the process is executable
122-
process, err := ml.startProcess(uri, modelID, serverAddress)
122+
process, err := ml.StartProcess(uri, modelID, serverAddress, o.envVars)
123123
if err != nil {
124124
xlog.Error("failed to launch", "error", err, "path", uri)
125125
return nil, err

pkg/model/loader_options.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ type Options struct {
2424
// by the caller using the vram estimation scaffolding. When non-zero it is
2525
// registered with the watchdog so size-aware eviction can rank models.
2626
modelSizeBytes int64
27+
28+
// envVars contains model-specific environment variables to pass to the backend process
29+
envVars map[string]string
2730
}
2831

2932
type Option func(*Options)
@@ -97,6 +100,12 @@ func WithModelSizeBytes(bytes int64) Option {
97100
}
98101
}
99102

103+
func WithEnvVars(envVars map[string]string) Option {
104+
return func(o *Options) {
105+
o.envVars = envVars
106+
}
107+
}
108+
100109
func NewOptions(opts ...Option) *Options {
101110
o := &Options{
102111
gRPCOptions: &pb.ModelOptions{},

pkg/model/process.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,11 +141,11 @@ func (ml *ModelLoader) GetGRPCPID(id string) (int, error) {
141141
// StartProcess starts a gRPC backend process and returns its process handle.
142142
// This is the public wrapper for the internal startProcess method, used by
143143
// the serve-backend CLI subcommand to start a backend on a specified address.
144-
func (ml *ModelLoader) StartProcess(grpcProcess, id string, serverAddress string, args ...string) (*process.Process, error) {
145-
return ml.startProcess(grpcProcess, id, serverAddress, args...)
144+
func (ml *ModelLoader) StartProcess(grpcProcess, id string, serverAddress string, envVars map[string]string, args ...string) (*process.Process, error) {
145+
return ml.startProcess(grpcProcess, id, serverAddress, envVars, args...)
146146
}
147147

148-
func (ml *ModelLoader) startProcess(grpcProcess, id string, serverAddress string, args ...string) (*process.Process, error) {
148+
func (ml *ModelLoader) startProcess(grpcProcess, id string, serverAddress string, envVars map[string]string, args ...string) (*process.Process, error) {
149149
// Make sure the process is executable
150150
// Check first if it has executable permissions
151151
if fi, err := os.Stat(grpcProcess); err == nil {
@@ -175,6 +175,13 @@ func (ml *ModelLoader) startProcess(grpcProcess, id string, serverAddress string
175175
// and the GPU would silently fall back to CPU). No-op for other backends.
176176
env = append(env, vulkanICDEnv(workDir)...)
177177

178+
// Add model-specific environment variables
179+
if envVars != nil {
180+
for key, value := range envVars {
181+
env = append(env, fmt.Sprintf("%s=%s", key, value))
182+
}
183+
}
184+
178185
grpcControlProcess := process.New(
179186
process.WithTemporaryStateDir(),
180187
process.WithName(filepath.Base(grpcProcess)),

0 commit comments

Comments
 (0)