Skip to content

Commit 8862e3c

Browse files
authored
feat: add node reconciler, allow to schedule to group of nodes, min/max autoscaler (#9186)
* always enable parallel requests Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * feat: add node reconciler, allow to schedule to group of nodes, min/max autoscaler Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * chore: move tests to ginkgo Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * chore(smart router): order by available vram Signed-off-by: Ettore Di Giacinto <mudler@localai.io> --------- Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
1 parent 80699a3 commit 8862e3c

30 files changed

Lines changed: 2366 additions & 394 deletions

core/application/config_file_watcher.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,6 @@ func readRuntimeSettingsJson(startupAppConfig config.ApplicationConfig) fileHand
199199
envWatchdogBusyTimeout := appConfig.WatchDogBusyTimeout == startupAppConfig.WatchDogBusyTimeout
200200
envSingleBackend := appConfig.SingleBackend == startupAppConfig.SingleBackend
201201
envMaxActiveBackends := appConfig.MaxActiveBackends == startupAppConfig.MaxActiveBackends
202-
envParallelRequests := appConfig.ParallelBackendRequests == startupAppConfig.ParallelBackendRequests
203202
envMemoryReclaimerEnabled := appConfig.MemoryReclaimerEnabled == startupAppConfig.MemoryReclaimerEnabled
204203
envMemoryReclaimerThreshold := appConfig.MemoryReclaimerThreshold == startupAppConfig.MemoryReclaimerThreshold
205204
envThreads := appConfig.Threads == startupAppConfig.Threads
@@ -271,9 +270,6 @@ func readRuntimeSettingsJson(startupAppConfig config.ApplicationConfig) fileHand
271270
appConfig.MaxActiveBackends = 0
272271
}
273272
}
274-
if settings.ParallelBackendRequests != nil && !envParallelRequests {
275-
appConfig.ParallelBackendRequests = *settings.ParallelBackendRequests
276-
}
277273
if settings.MemoryReclaimerEnabled != nil && !envMemoryReclaimerEnabled {
278274
appConfig.MemoryReclaimerEnabled = *settings.MemoryReclaimerEnabled
279275
if appConfig.MemoryReclaimerEnabled {

core/application/distributed.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"io"
88
"strings"
99
"sync"
10+
"time"
1011

1112
"github.com/google/uuid"
1213
"github.com/mudler/LocalAI/core/config"
@@ -28,6 +29,7 @@ type DistributedServices struct {
2829
Registry *nodes.NodeRegistry
2930
Router *nodes.SmartRouter
3031
Health *nodes.HealthMonitor
32+
Reconciler *nodes.ReplicaReconciler
3133
JobStore *jobs.JobStore
3234
Dispatcher *jobs.Dispatcher
3335
AgentStore *agents.AgentStore
@@ -240,6 +242,16 @@ func initDistributed(cfg *config.ApplicationConfig, authDB *gorm.DB) (*Distribut
240242
DB: authDB,
241243
})
242244

245+
// Create ReplicaReconciler for auto-scaling model replicas
246+
reconciler := nodes.NewReplicaReconciler(nodes.ReplicaReconcilerOptions{
247+
Registry: registry,
248+
Scheduler: router,
249+
Unloader: remoteUnloader,
250+
DB: authDB,
251+
Interval: 30 * time.Second,
252+
ScaleDownDelay: 5 * time.Minute,
253+
})
254+
243255
// Create ModelRouterAdapter to wire into ModelLoader
244256
modelAdapter := nodes.NewModelRouterAdapter(router)
245257

@@ -250,6 +262,7 @@ func initDistributed(cfg *config.ApplicationConfig, authDB *gorm.DB) (*Distribut
250262
Registry: registry,
251263
Router: router,
252264
Health: healthMon,
265+
Reconciler: reconciler,
253266
JobStore: jobStore,
254267
Dispatcher: dispatcher,
255268
AgentStore: agentStore,

core/application/startup.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,10 @@ func New(opts ...config.AppOption) (*Application, error) {
158158
application.modelLoader.SetModelStore(distStore)
159159
// Start health monitor
160160
distSvc.Health.Start(options.Context)
161+
// Start replica reconciler for auto-scaling model replicas
162+
if distSvc.Reconciler != nil {
163+
go distSvc.Reconciler.Run(options.Context)
164+
}
161165
// In distributed mode, MCP CI jobs are executed by agent workers (not the frontend)
162166
// because the frontend can't create MCP sessions (e.g., stdio servers using docker).
163167
// The dispatcher still subscribes to jobs.new for persistence (result/progress subs)
@@ -439,11 +443,6 @@ func loadRuntimeSettingsFromFile(options *config.ApplicationConfig) {
439443
}
440444
}
441445
}
442-
if settings.ParallelBackendRequests != nil {
443-
if !options.ParallelBackendRequests {
444-
options.ParallelBackendRequests = *settings.ParallelBackendRequests
445-
}
446-
}
447446
if settings.MemoryReclaimerEnabled != nil {
448447
// Only apply if current value is default (false), suggesting it wasn't set from env var
449448
if !options.MemoryReclaimerEnabled {

core/backend/options.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,7 @@ func ModelOptions(c config.ModelConfig, so *config.ApplicationConfig, opts ...mo
5959
grpcOpts := grpcModelOpts(c, so.SystemState.Model.ModelsPath)
6060
defOpts = append(defOpts, model.WithLoadGRPCLoadModelOpts(grpcOpts))
6161

62-
if so.ParallelBackendRequests {
63-
defOpts = append(defOpts, model.EnableParallelRequests)
64-
}
62+
defOpts = append(defOpts, model.EnableParallelRequests)
6563

6664
if c.GRPC.Attempts != 0 {
6765
defOpts = append(defOpts, model.WithGRPCAttempts(c.GRPC.Attempts))

0 commit comments

Comments
 (0)