|
|
package application |
|
|
|
|
|
import ( |
|
|
"encoding/json" |
|
|
"fmt" |
|
|
"os" |
|
|
"path/filepath" |
|
|
"time" |
|
|
|
|
|
"github.com/mudler/LocalAI/core/backend" |
|
|
"github.com/mudler/LocalAI/core/config" |
|
|
"github.com/mudler/LocalAI/core/gallery" |
|
|
"github.com/mudler/LocalAI/core/services" |
|
|
coreStartup "github.com/mudler/LocalAI/core/startup" |
|
|
"github.com/mudler/LocalAI/internal" |
|
|
|
|
|
"github.com/mudler/LocalAI/pkg/model" |
|
|
"github.com/mudler/LocalAI/pkg/xsysinfo" |
|
|
"github.com/mudler/xlog" |
|
|
) |
|
|
|
|
|
func New(opts ...config.AppOption) (*Application, error) { |
|
|
options := config.NewApplicationConfig(opts...) |
|
|
|
|
|
|
|
|
|
|
|
startupConfigCopy := *options |
|
|
application := newApplication(options) |
|
|
application.startupConfig = &startupConfigCopy |
|
|
|
|
|
xlog.Info("Starting LocalAI", "threads", options.Threads, "modelsPath", options.SystemState.Model.ModelsPath) |
|
|
xlog.Info("LocalAI version", "version", internal.PrintableVersion()) |
|
|
|
|
|
if err := application.start(); err != nil { |
|
|
return nil, err |
|
|
} |
|
|
|
|
|
caps, err := xsysinfo.CPUCapabilities() |
|
|
if err == nil { |
|
|
xlog.Debug("CPU capabilities", "capabilities", caps) |
|
|
|
|
|
} |
|
|
gpus, err := xsysinfo.GPUs() |
|
|
if err == nil { |
|
|
xlog.Debug("GPU count", "count", len(gpus)) |
|
|
for _, gpu := range gpus { |
|
|
xlog.Debug("GPU", "gpu", gpu.String()) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if options.SystemState.Model.ModelsPath == "" { |
|
|
return nil, fmt.Errorf("models path cannot be empty") |
|
|
} |
|
|
|
|
|
err = os.MkdirAll(options.SystemState.Model.ModelsPath, 0750) |
|
|
if err != nil { |
|
|
return nil, fmt.Errorf("unable to create ModelPath: %q", err) |
|
|
} |
|
|
if options.GeneratedContentDir != "" { |
|
|
err := os.MkdirAll(options.GeneratedContentDir, 0750) |
|
|
if err != nil { |
|
|
return nil, fmt.Errorf("unable to create ImageDir: %q", err) |
|
|
} |
|
|
} |
|
|
if options.UploadDir != "" { |
|
|
err := os.MkdirAll(options.UploadDir, 0750) |
|
|
if err != nil { |
|
|
return nil, fmt.Errorf("unable to create UploadDir: %q", err) |
|
|
} |
|
|
} |
|
|
|
|
|
if err := coreStartup.InstallModels(options.Context, application.GalleryService(), options.Galleries, options.BackendGalleries, options.SystemState, application.ModelLoader(), options.EnforcePredownloadScans, options.AutoloadBackendGalleries, nil, options.ModelsURL...); err != nil { |
|
|
xlog.Error("error installing models", "error", err) |
|
|
} |
|
|
|
|
|
for _, backend := range options.ExternalBackends { |
|
|
if err := services.InstallExternalBackend(options.Context, options.BackendGalleries, options.SystemState, application.ModelLoader(), nil, backend, "", ""); err != nil { |
|
|
xlog.Error("error installing external backend", "error", err) |
|
|
} |
|
|
} |
|
|
|
|
|
configLoaderOpts := options.ToConfigLoaderOptions() |
|
|
|
|
|
if err := application.ModelConfigLoader().LoadModelConfigsFromPath(options.SystemState.Model.ModelsPath, configLoaderOpts...); err != nil { |
|
|
xlog.Error("error loading config files", "error", err) |
|
|
} |
|
|
|
|
|
if err := gallery.RegisterBackends(options.SystemState, application.ModelLoader()); err != nil { |
|
|
xlog.Error("error registering external backends", "error", err) |
|
|
} |
|
|
|
|
|
if options.ConfigFile != "" { |
|
|
if err := application.ModelConfigLoader().LoadMultipleModelConfigsSingleFile(options.ConfigFile, configLoaderOpts...); err != nil { |
|
|
xlog.Error("error loading config file", "error", err) |
|
|
} |
|
|
} |
|
|
|
|
|
if err := application.ModelConfigLoader().Preload(options.SystemState.Model.ModelsPath); err != nil { |
|
|
xlog.Error("error downloading models", "error", err) |
|
|
} |
|
|
|
|
|
if options.PreloadJSONModels != "" { |
|
|
if err := services.ApplyGalleryFromString(options.SystemState, application.ModelLoader(), options.EnforcePredownloadScans, options.AutoloadBackendGalleries, options.Galleries, options.BackendGalleries, options.PreloadJSONModels); err != nil { |
|
|
return nil, err |
|
|
} |
|
|
} |
|
|
|
|
|
if options.PreloadModelsFromPath != "" { |
|
|
if err := services.ApplyGalleryFromFile(options.SystemState, application.ModelLoader(), options.EnforcePredownloadScans, options.AutoloadBackendGalleries, options.Galleries, options.BackendGalleries, options.PreloadModelsFromPath); err != nil { |
|
|
return nil, err |
|
|
} |
|
|
} |
|
|
|
|
|
if options.Debug { |
|
|
for _, v := range application.ModelConfigLoader().GetAllModelsConfigs() { |
|
|
xlog.Debug("Model", "name", v.Name, "config", v) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if options.DynamicConfigsDir != "" { |
|
|
loadRuntimeSettingsFromFile(options) |
|
|
} |
|
|
|
|
|
|
|
|
go func() { |
|
|
<-options.Context.Done() |
|
|
xlog.Debug("Context canceled, shutting down") |
|
|
err := application.ModelLoader().StopAllGRPC() |
|
|
if err != nil { |
|
|
xlog.Error("error while stopping all grpc backends", "error", err) |
|
|
} |
|
|
}() |
|
|
|
|
|
|
|
|
initializeWatchdog(application, options) |
|
|
|
|
|
if options.LoadToMemory != nil && !options.SingleBackend { |
|
|
for _, m := range options.LoadToMemory { |
|
|
cfg, err := application.ModelConfigLoader().LoadModelConfigFileByNameDefaultOptions(m, options) |
|
|
if err != nil { |
|
|
return nil, err |
|
|
} |
|
|
|
|
|
xlog.Debug("Auto loading model into memory from file", "model", m, "file", cfg.Model) |
|
|
|
|
|
o := backend.ModelOptions(*cfg, options) |
|
|
|
|
|
var backendErr error |
|
|
_, backendErr = application.ModelLoader().Load(o...) |
|
|
if backendErr != nil { |
|
|
return nil, err |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
startWatcher(options) |
|
|
|
|
|
xlog.Info("core/startup process completed!") |
|
|
return application, nil |
|
|
} |
|
|
|
|
|
func startWatcher(options *config.ApplicationConfig) { |
|
|
if options.DynamicConfigsDir == "" { |
|
|
|
|
|
return |
|
|
} |
|
|
|
|
|
if _, err := os.Stat(options.DynamicConfigsDir); err != nil { |
|
|
if os.IsNotExist(err) { |
|
|
|
|
|
if err := os.MkdirAll(options.DynamicConfigsDir, 0700); err != nil { |
|
|
xlog.Error("failed creating DynamicConfigsDir", "error", err) |
|
|
} |
|
|
} else { |
|
|
|
|
|
xlog.Error("failed to read DynamicConfigsDir, watcher will not be started", "error", err) |
|
|
return |
|
|
} |
|
|
} |
|
|
|
|
|
configHandler := newConfigFileHandler(options) |
|
|
if err := configHandler.Watch(); err != nil { |
|
|
xlog.Error("failed creating watcher", "error", err) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func loadRuntimeSettingsFromFile(options *config.ApplicationConfig) { |
|
|
settingsFile := filepath.Join(options.DynamicConfigsDir, "runtime_settings.json") |
|
|
fileContent, err := os.ReadFile(settingsFile) |
|
|
if err != nil { |
|
|
if os.IsNotExist(err) { |
|
|
xlog.Debug("runtime_settings.json not found, using defaults") |
|
|
return |
|
|
} |
|
|
xlog.Warn("failed to read runtime_settings.json", "error", err) |
|
|
return |
|
|
} |
|
|
|
|
|
var settings config.RuntimeSettings |
|
|
|
|
|
if err := json.Unmarshal(fileContent, &settings); err != nil { |
|
|
xlog.Warn("failed to parse runtime_settings.json", "error", err) |
|
|
return |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if settings.WatchdogIdleEnabled != nil { |
|
|
|
|
|
if !options.WatchDogIdle { |
|
|
options.WatchDogIdle = *settings.WatchdogIdleEnabled |
|
|
if options.WatchDogIdle { |
|
|
options.WatchDog = true |
|
|
} |
|
|
} |
|
|
} |
|
|
if settings.WatchdogBusyEnabled != nil { |
|
|
if !options.WatchDogBusy { |
|
|
options.WatchDogBusy = *settings.WatchdogBusyEnabled |
|
|
if options.WatchDogBusy { |
|
|
options.WatchDog = true |
|
|
} |
|
|
} |
|
|
} |
|
|
if settings.WatchdogIdleTimeout != nil { |
|
|
|
|
|
if options.WatchDogIdleTimeout == 0 { |
|
|
dur, err := time.ParseDuration(*settings.WatchdogIdleTimeout) |
|
|
if err == nil { |
|
|
options.WatchDogIdleTimeout = dur |
|
|
} else { |
|
|
xlog.Warn("invalid watchdog idle timeout in runtime_settings.json", "error", err, "timeout", *settings.WatchdogIdleTimeout) |
|
|
} |
|
|
} |
|
|
} |
|
|
if settings.WatchdogBusyTimeout != nil { |
|
|
if options.WatchDogBusyTimeout == 0 { |
|
|
dur, err := time.ParseDuration(*settings.WatchdogBusyTimeout) |
|
|
if err == nil { |
|
|
options.WatchDogBusyTimeout = dur |
|
|
} else { |
|
|
xlog.Warn("invalid watchdog busy timeout in runtime_settings.json", "error", err, "timeout", *settings.WatchdogBusyTimeout) |
|
|
} |
|
|
} |
|
|
} |
|
|
if settings.WatchdogInterval != nil { |
|
|
if options.WatchDogInterval == 0 { |
|
|
dur, err := time.ParseDuration(*settings.WatchdogInterval) |
|
|
if err == nil { |
|
|
options.WatchDogInterval = dur |
|
|
} else { |
|
|
xlog.Warn("invalid watchdog interval in runtime_settings.json", "error", err, "interval", *settings.WatchdogInterval) |
|
|
options.WatchDogInterval = model.DefaultWatchdogInterval |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
if settings.MaxActiveBackends != nil { |
|
|
|
|
|
if options.MaxActiveBackends == 0 { |
|
|
options.MaxActiveBackends = *settings.MaxActiveBackends |
|
|
|
|
|
options.SingleBackend = (*settings.MaxActiveBackends == 1) |
|
|
} |
|
|
} else if settings.SingleBackend != nil { |
|
|
|
|
|
if !options.SingleBackend { |
|
|
options.SingleBackend = *settings.SingleBackend |
|
|
if *settings.SingleBackend { |
|
|
options.MaxActiveBackends = 1 |
|
|
} |
|
|
} |
|
|
} |
|
|
if settings.ParallelBackendRequests != nil { |
|
|
if !options.ParallelBackendRequests { |
|
|
options.ParallelBackendRequests = *settings.ParallelBackendRequests |
|
|
} |
|
|
} |
|
|
if settings.MemoryReclaimerEnabled != nil { |
|
|
|
|
|
if !options.MemoryReclaimerEnabled { |
|
|
options.MemoryReclaimerEnabled = *settings.MemoryReclaimerEnabled |
|
|
if options.MemoryReclaimerEnabled { |
|
|
options.WatchDog = true |
|
|
} |
|
|
} |
|
|
} |
|
|
if settings.MemoryReclaimerThreshold != nil { |
|
|
|
|
|
if options.MemoryReclaimerThreshold == 0 { |
|
|
options.MemoryReclaimerThreshold = *settings.MemoryReclaimerThreshold |
|
|
} |
|
|
} |
|
|
if settings.AgentJobRetentionDays != nil { |
|
|
|
|
|
if options.AgentJobRetentionDays == 0 { |
|
|
options.AgentJobRetentionDays = *settings.AgentJobRetentionDays |
|
|
} |
|
|
} |
|
|
if !options.WatchDogIdle && !options.WatchDogBusy { |
|
|
if settings.WatchdogEnabled != nil && *settings.WatchdogEnabled { |
|
|
options.WatchDog = true |
|
|
} |
|
|
} |
|
|
|
|
|
xlog.Debug("Runtime settings loaded from runtime_settings.json") |
|
|
} |
|
|
|
|
|
|
|
|
func initializeWatchdog(application *Application, options *config.ApplicationConfig) { |
|
|
|
|
|
lruLimit := options.GetEffectiveMaxActiveBackends() |
|
|
|
|
|
|
|
|
if options.WatchDog || lruLimit > 0 || options.MemoryReclaimerEnabled { |
|
|
wd := model.NewWatchDog( |
|
|
model.WithProcessManager(application.ModelLoader()), |
|
|
model.WithBusyTimeout(options.WatchDogBusyTimeout), |
|
|
model.WithIdleTimeout(options.WatchDogIdleTimeout), |
|
|
model.WithWatchdogInterval(options.WatchDogInterval), |
|
|
model.WithBusyCheck(options.WatchDogBusy), |
|
|
model.WithIdleCheck(options.WatchDogIdle), |
|
|
model.WithLRULimit(lruLimit), |
|
|
model.WithMemoryReclaimer(options.MemoryReclaimerEnabled, options.MemoryReclaimerThreshold), |
|
|
model.WithForceEvictionWhenBusy(options.ForceEvictionWhenBusy), |
|
|
) |
|
|
application.ModelLoader().SetWatchDog(wd) |
|
|
|
|
|
|
|
|
application.ModelLoader().SetLRUEvictionRetrySettings( |
|
|
options.LRUEvictionMaxRetries, |
|
|
options.LRUEvictionRetryInterval, |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if options.WatchDogBusy || options.WatchDogIdle || options.MemoryReclaimerEnabled { |
|
|
go wd.Run() |
|
|
} |
|
|
|
|
|
go func() { |
|
|
<-options.Context.Done() |
|
|
xlog.Debug("Context canceled, shutting down") |
|
|
wd.Shutdown() |
|
|
}() |
|
|
} |
|
|
} |
|
|
|