mirror of
https://github.com/mudler/LocalAI.git
synced 2026-07-30 18:09:05 -04:00
backendLoader logged "BackendLoader starting" at INFO as its very first statement, unconditionally. That reads as "a model is being loaded", but backendLoader is not only a load path: in distributed mode Load() deliberately bypasses the local cache and calls backendLoader on every inference request so SmartRouter can re-pick a replica per request. The model is already resident, no process is spawned, and nothing is loaded, yet the banner fires at request rate. On a live cluster this produced ~5 "BackendLoader starting" lines per second for a single embedding model, sustained, starting 22 seconds after the load had already completed. The model was state=loaded with in_flight=0 and exactly one backend process on the worker. It looked exactly like a retry storm and cost real debugging time during an unrelated production investigation. The adjacent "effective runtime tuning" banner, documented as "logged once per load", had the same problem for the same reason. Emit both banners at INFO only when the model is not already resident, and keep the per-call trace at DEBUG for anyone following the routing path. isResident is a plain store lookup with no health probe and no eviction, so it is safe on the per-request hot path (unlike checkIsLoaded, which probes and can evict). Same class of defect as #10985: a log line that sends the reader after the wrong thing. Assisted-by: Claude Code:claude-opus-4-8[1m] [Read] [Edit] [Bash] Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Co-authored-by: Ettore Di Giacinto <mudler@localai.io>
103 lines
3.7 KiB
Go
103 lines
3.7 KiB
Go
package model
|
|
|
|
import (
|
|
"bytes"
|
|
"log/slog"
|
|
|
|
pb "github.com/mudler/LocalAI/pkg/grpc/proto"
|
|
"github.com/mudler/LocalAI/pkg/system"
|
|
"github.com/mudler/xlog"
|
|
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
)
|
|
|
|
// backendLoader is not a synonym for "a model is being loaded". In distributed
|
|
// mode Load() deliberately bypasses the local cache and calls backendLoader on
|
|
// every inference request so SmartRouter can re-pick a replica per request, so
|
|
// the function runs at request rate against an already-resident model. Emitting
|
|
// the load banner unconditionally made ordinary embedding traffic (~5 req/s)
|
|
// look like a retry storm in the INFO log and sent an engineer chasing a
|
|
// non-existent hot loop during an unrelated production investigation. INFO must
|
|
// mark a genuine cold load only; the per-call trace belongs at DEBUG.
|
|
var _ = Describe("backendLoader load logging", func() {
|
|
var (
|
|
ml *ModelLoader
|
|
captured *bytes.Buffer
|
|
)
|
|
|
|
BeforeEach(func() {
|
|
systemState, err := system.GetSystemState(system.WithModelPath(GinkgoT().TempDir()))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
ml = NewModelLoader(systemState)
|
|
|
|
// Capture at info level so a debug-level emission is filtered out: the
|
|
// assertions then fail on severity, not merely on wording.
|
|
captured = &bytes.Buffer{}
|
|
handler := slog.NewTextHandler(captured, &slog.HandlerOptions{Level: slog.LevelInfo})
|
|
xlog.SetLogger(xlog.NewLoggerWithHandler(handler, xlog.LogLevelInfo))
|
|
})
|
|
|
|
AfterEach(func() {
|
|
// xlog exposes no getter for the package logger, so restore the same
|
|
// default the suite entrypoint installs rather than the prior value.
|
|
xlog.SetLogger(xlog.NewLogger(xlog.LogLevel("info"), "text"))
|
|
})
|
|
|
|
Context("when the model is already resident", func() {
|
|
BeforeEach(func() {
|
|
resident := NewModel("resident-model", "127.0.0.1:65535", nil)
|
|
// Skip the gRPC health probe so the resident model survives the
|
|
// lookup without a live backend behind the address.
|
|
resident.MarkHealthy()
|
|
ml.mu.Lock()
|
|
ml.store.Set("resident-model", resident)
|
|
ml.mu.Unlock()
|
|
})
|
|
|
|
It("does not announce a load at info level", func() {
|
|
_, err := ml.backendLoader(
|
|
WithModelID("resident-model"),
|
|
WithModel("resident-model"),
|
|
WithBackendString("llama-cpp"),
|
|
WithLoadGRPCLoadModelOpts(&pb.ModelOptions{ContextSize: 4096}),
|
|
)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
Expect(captured.String()).ToNot(ContainSubstring("BackendLoader starting"),
|
|
"a request served by an already-resident model must not look like a cold load")
|
|
})
|
|
|
|
It("does not repeat the effective runtime tuning banner at info level", func() {
|
|
_, err := ml.backendLoader(
|
|
WithModelID("resident-model"),
|
|
WithModel("resident-model"),
|
|
WithBackendString("llama-cpp"),
|
|
WithLoadGRPCLoadModelOpts(&pb.ModelOptions{ContextSize: 4096}),
|
|
)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
Expect(captured.String()).ToNot(ContainSubstring("effective runtime tuning"),
|
|
"the tuning banner documents what a load will run with, so it belongs to a load")
|
|
})
|
|
})
|
|
|
|
Context("when the model is not resident", func() {
|
|
It("still announces the cold load at info level", func() {
|
|
// The load itself fails (no such backend is installed); what is
|
|
// pinned here is that the banner is emitted before that, so
|
|
// suppressing the warm case does not silence real loads.
|
|
_, err := ml.backendLoader(
|
|
WithModelID("cold-model"),
|
|
WithModel("cold-model"),
|
|
WithBackendString("definitely-not-an-installed-backend"),
|
|
WithLoadGRPCLoadModelOpts(&pb.ModelOptions{ContextSize: 4096}),
|
|
)
|
|
Expect(err).To(HaveOccurred())
|
|
|
|
Expect(captured.String()).To(ContainSubstring("BackendLoader starting"))
|
|
Expect(captured.String()).To(ContainSubstring("effective runtime tuning"))
|
|
})
|
|
})
|
|
})
|