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.
This commit is contained in:
Ettore Di Giacinto
2026-04-12 08:11:38 +00:00
parent 40b1c6f943
commit a30719f04a
4 changed files with 82 additions and 47 deletions

View File

@@ -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)
}
}

View File

@@ -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
}
}

View File

@@ -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)
}
}

View File

@@ -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()
}