From a30719f04a601f967c6fa6e60dbbe1ce041100b7 Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Sun, 12 Apr 2026 08:11:38 +0000 Subject: [PATCH] refactor(config): introduce backend hook system and migrate llama-cpp defaults Adds RegisterBackendHook/runBackendHooks so each backend can register default-filling functions that run during ModelConfig.SetDefaults(). Migrates the existing GGUF guessing logic into hooks_llamacpp.go, registered for both 'llama-cpp' and the empty backend (auto-detect). Removes the old guesser.go shim. --- core/config/backend_hooks.go | 30 +++++++++++++++++++++++ core/config/guesser.go | 46 ----------------------------------- core/config/hooks_llamacpp.go | 46 +++++++++++++++++++++++++++++++++++ core/config/model_config.go | 7 +++++- 4 files changed, 82 insertions(+), 47 deletions(-) create mode 100644 core/config/backend_hooks.go delete mode 100644 core/config/guesser.go create mode 100644 core/config/hooks_llamacpp.go diff --git a/core/config/backend_hooks.go b/core/config/backend_hooks.go new file mode 100644 index 000000000..8b2403cbb --- /dev/null +++ b/core/config/backend_hooks.go @@ -0,0 +1,30 @@ +package config + +// BackendDefaultsHook is called during Prepare() and can modify cfg. +// Only fills in values that are not already set by the user. +type BackendDefaultsHook func(cfg *ModelConfig, modelPath string) + +var backendHooks = map[string][]BackendDefaultsHook{} + +// RegisterBackendHook registers a hook for a backend name. +// Special keys: +// - "*" = global catch-all, runs for EVERY backend (before specific hooks) +// - "" = runs only when cfg.Backend is empty (auto-detect case) +// - "vllm", "llama-cpp" etc. = runs only for that specific backend +// +// Multiple hooks per key are supported; they run in registration order. +func RegisterBackendHook(backend string, hook BackendDefaultsHook) { + backendHooks[backend] = append(backendHooks[backend], hook) +} + +// runBackendHooks executes hooks in order: +// 1. "*" (global) hooks for every backend +// 2. Backend-specific hooks for cfg.Backend (includes "" when backend is empty) +func runBackendHooks(cfg *ModelConfig, modelPath string) { + for _, h := range backendHooks["*"] { + h(cfg, modelPath) + } + for _, h := range backendHooks[cfg.Backend] { + h(cfg, modelPath) + } +} diff --git a/core/config/guesser.go b/core/config/guesser.go deleted file mode 100644 index e4ca5b141..000000000 --- a/core/config/guesser.go +++ /dev/null @@ -1,46 +0,0 @@ -package config - -import ( - "os" - "path/filepath" - - gguf "github.com/gpustack/gguf-parser-go" - "github.com/mudler/xlog" -) - -func guessDefaultsFromFile(cfg *ModelConfig, modelPath string, defaultCtx int) { - if os.Getenv("LOCALAI_DISABLE_GUESSING") == "true" { - xlog.Debug("guessDefaultsFromFile: guessing disabled with LOCALAI_DISABLE_GUESSING") - return - } - - if modelPath == "" { - xlog.Debug("guessDefaultsFromFile: modelPath is empty") - return - } - - // We try to guess only if we don't have a template defined already - guessPath := filepath.Join(modelPath, cfg.ModelFileName()) - - defer func() { - if r := recover(); r != nil { - xlog.Error("guessDefaultsFromFile: panic while parsing gguf file") - } - }() - - defer func() { - if cfg.ContextSize == nil { - if defaultCtx == 0 { - defaultCtx = defaultContextSize - } - cfg.ContextSize = &defaultCtx - } - }() - - // try to parse the gguf file - f, err := gguf.ParseGGUFFile(guessPath) - if err == nil { - guessGGUFFromFile(cfg, f, defaultCtx) - return - } -} diff --git a/core/config/hooks_llamacpp.go b/core/config/hooks_llamacpp.go new file mode 100644 index 000000000..7c2640cee --- /dev/null +++ b/core/config/hooks_llamacpp.go @@ -0,0 +1,46 @@ +package config + +import ( + "os" + "path/filepath" + + gguf "github.com/gpustack/gguf-parser-go" + "github.com/mudler/xlog" +) + +func init() { + // Register for both explicit llama-cpp and empty backend (auto-detect from GGUF file) + RegisterBackendHook("llama-cpp", llamaCppDefaults) + RegisterBackendHook("", llamaCppDefaults) +} + +func llamaCppDefaults(cfg *ModelConfig, modelPath string) { + if os.Getenv("LOCALAI_DISABLE_GUESSING") == "true" { + xlog.Debug("llamaCppDefaults: guessing disabled") + return + } + if modelPath == "" { + return + } + + guessPath := filepath.Join(modelPath, cfg.ModelFileName()) + + defer func() { + if r := recover(); r != nil { + xlog.Error("llamaCppDefaults: panic while parsing gguf file") + } + }() + + // Default context size if not set, regardless of whether GGUF parsing succeeds + defer func() { + if cfg.ContextSize == nil { + ctx := defaultContextSize + cfg.ContextSize = &ctx + } + }() + + f, err := gguf.ParseGGUFFile(guessPath) + if err == nil { + guessGGUFFromFile(cfg, f, 0) + } +} diff --git a/core/config/model_config.go b/core/config/model_config.go index 5f1780b76..4185d4f3f 100644 --- a/core/config/model_config.go +++ b/core/config/model_config.go @@ -497,7 +497,12 @@ func (cfg *ModelConfig) SetDefaults(opts ...ConfigLoaderOption) { cfg.Debug = &trueV } - guessDefaultsFromFile(cfg, lo.modelPath, ctx) + // If a context size was provided via LoadOptions, apply it before hooks so they + // don't override it with their own defaults. + if ctx != 0 && cfg.ContextSize == nil { + cfg.ContextSize = &ctx + } + runBackendHooks(cfg, lo.modelPath) cfg.syncKnownUsecasesFromString() }