fix(config): cap auto-derived context to fit VRAM#10696
Merged
Conversation
When a model is imported without an explicit context_size, the GGUF importer defaulted the model's context to its full trained window (n_ctx_train). For long-context models (128k / 256k / 1M) that KV cache cannot fit a consumer GPU, so the backend aborts on load (exitCode=-1) even though the model file is perfectly fine. Reproduced live: gemma-4-26b-a4b-it-qat-q4_0 defaulted to context=262144 and qwythos-9b-claude-mythos-5-1m to 1048576, both aborting on a 20 GB card. Instead of chasing the trained max, auto-derive a conservative default: min(trainedMax, DefaultAutoContextSize=8192). A small model keeps its trained window; a long-context model caps at 8k and users opt into more via context_size. This cap applies always, including CPU / unknown-VRAM hosts, so it never regresses those paths. Per-device VRAM is used only as a DOWNWARD safety: when a per-device ceiling is detected (xsysinfo.MinPerGPUVRAM) and even the 8k cap would not fit it with headroom, step down through candidate contexts to the largest that fits, floored at DefaultContextSize. When VRAM is unknown (0) or no GPU is detected we do NOT clamp — the bug is GPU OOM and the 8k cap is already safe, so detection gaps must not shrink the window. The footprint estimate reuses gpustack/gguf-parser-go's EstimateLLaMACppRun at a given context with all layers offloaded, taking the per-device NonUMA VRAM figure. The estimate and VRAM detection are package vars so tests inject deterministic values. Explicit context_size always wins (guessGGUFFromFile only acts when it is nil). Assisted-by: Claude:claude-opus-4-8 [golangci-lint go-test] Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
dddd76e to
fa15af0
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Symptom
When a model is imported without an explicit
context_size, LocalAI defaults the model's context to the GGUF's full trained window (n_ctx_train). For long-context models (128k / 256k / 1M) that KV cache cannot fit the GPU and the backend aborts on load (exitCode=-1), even though the model file is fine.Reproduced live on a 20 GB card:
gemma-4-26b-a4b-it-qat-q4_0defaulted tocontext=262144→ abortqwythos-9b-claude-mythos-5-1mdefaulted tocontext=1048576→ abortRoot cause
core/config/gguf.go'sguessGGUFFromFilesetcfg.ContextSizetof.EstimateLLaMACppRun().ContextSize— the model's max trained context, unbounded — whenever the user leftcontext_sizeunset.The fix
Auto-derive a conservative default instead of chasing the trained max:
min(trainedMax, DefaultAutoContextSize)whereDefaultAutoContextSize = 8192. A small model (trained < 8k) keeps its trained window; a long-context model caps at 8k. Users opt into more via an explicitcontext_size. This applies on every host, including CPU / unknown-VRAM, so it never regresses those paths.xsysinfo.MinPerGPUVRAM, the smallest card — matchinghardware_defaults.go'slocalGPU) and even the 8k cap would not fit it with ~20% headroom, step down through candidate contexts to the largest that fits, floored atDefaultContextSize(4096). The per-context footprint is estimated withgpustack/gguf-parser-go'sEstimateLLaMACppRun(all layers offloaded, per-deviceNonUMAfigure).VRAM-unknown fallback rationale
If per-device VRAM is unknown (
0) or no GPU is detected, we do not clamp — the bug is GPU OOM-on-load, and the 8k base cap is already safe. Clamping on a missing/zero reading would risk shrinking the window on CPU / unified-memory / detection-gap hosts, so those paths are deliberately left at the base cap. This bounds the blast radius of the change.Explicit
context_sizealways wins (guessGGUFFromFileonly acts when it isnil).Files changed
core/config/defaults.go— newDefaultAutoContextSize = 8192constant (with WHY comment).core/config/gguf.go—guessGGUFFromFilenow routes the auto-derived context throughautoContextSize.core/config/context_fit.go— new:autoContextSize, the headroom fit check, and the injectableperDeviceVRAM/estimateContextVRAMseams.core/config/context_fit_internal_test.go— new Ginkgo v2 specs.The estimate and VRAM detection are package vars so tests inject deterministic values (mirrors the
localGPUseam inhardware_defaults_internal_test.go).Test coverage (Ginkgo v2 + Gomega)
DefaultAutoContextSize(8192), not the trained max;DefaultContextSize, and the chosen context's footprint actually fits VRAM with headroom;DefaultContextSize;context_sizeset →guessGGUFFromFileleaves it untouched.go test ./core/config/...→ 276 specs pass.golangci-lint run ./core/config/→ 0 issues.Notes
core/config(gguf.go+ newcontext_fit.go+defaults.go); does not touchEffectiveBatchSize,PhysicalBatchForContext,largeContextForDevice, orApplyHardwareDefaults, to avoid conflicts with the concurrent embedding-batch VRAM-cap PR.core.hooksPath=/dev/null); CI runs the full suite.Signed-off-by/Co-Authored-By— please sign off on merge.