mirror of
https://github.com/mudler/LocalAI.git
synced 2026-07-31 02:18:50 -04:00
fix(ollama): cap num_ctx so it cannot wrap negative when cast to int32 (#11032)
* fix(ollama): cap num_ctx so it cannot wrap negative when cast to int32 applyOllamaOptions copied a client-supplied options.num_ctx straight into cfg.ContextSize with only a > 0 check. That value is later cast to int32 before it reaches the backend (core/backend/options.go), so a num_ctx above math.MaxInt32 silently wrapped into a negative context size that was then sent to the LoadModel gRPC call. Both /api/chat and /api/generate share applyOllamaOptions, so both endpoints were affected. Cap num_ctx at math.MaxInt32 so the later cast stays positive, and add internal regression coverage for the overflow, in-range, and unset cases. num_ctx remains an intentional user override, so this does not re-impose the hardware-aware auto context clamp; that policy choice is left to maintainers. Fixes #11022 Signed-off-by: Tai An <antai12232931@outlook.com> * fix(ollama): clamp num_ctx to model context ceiling, not just int32 Per review on #11032: capping only at math.MaxInt32 still let an unauthenticated request replace the hardware/model-derived context limit with ~2.1B tokens, so a real backend could attempt a catastrophic KV-cache allocation. Treat any existing positive cfg.ContextSize as the server ceiling and clamp num_ctx down to it (smaller values still honored), while retaining the int32-safe bound when no smaller ceiling exists. Shared by /api/chat and /api/generate via applyOllamaOptions. Add regression coverage proving num_ctx=2,000,000,000 cannot replace an existing 4096/8192 ceiling. Signed-off-by: Tai An <antai12232931@outlook.com> --------- Signed-off-by: Tai An <antai12232931@outlook.com>
This commit is contained in:
@@ -3,6 +3,7 @@ package ollama
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"github.com/mudler/LocalAI/core/config"
|
"github.com/mudler/LocalAI/core/config"
|
||||||
@@ -57,7 +58,25 @@ func applyOllamaOptions(opts *schema.OllamaOptions, cfg *config.ModelConfig) {
|
|||||||
cfg.StopWords = append(cfg.StopWords, opts.Stop...)
|
cfg.StopWords = append(cfg.StopWords, opts.Stop...)
|
||||||
}
|
}
|
||||||
if opts.NumCtx > 0 {
|
if opts.NumCtx > 0 {
|
||||||
cfg.ContextSize = &opts.NumCtx
|
numCtx := opts.NumCtx
|
||||||
|
// The model configuration / hardware-aware defaults have already
|
||||||
|
// populated cfg.ContextSize by the time we get here. Treat any
|
||||||
|
// existing positive value as the server-side ceiling: an
|
||||||
|
// unauthenticated client must never be able to *raise* the context
|
||||||
|
// window, since that drives KV-cache allocation and an oversized
|
||||||
|
// value (e.g. 2,000,000,000) can trigger a catastrophic OOM. A
|
||||||
|
// smaller num_ctx is still honored. See issue #11022.
|
||||||
|
if cfg.ContextSize != nil && *cfg.ContextSize > 0 && numCtx > *cfg.ContextSize {
|
||||||
|
numCtx = *cfg.ContextSize
|
||||||
|
}
|
||||||
|
// Regardless of the ceiling, keep the value int32-safe: ContextSize is
|
||||||
|
// cast to int32 before it reaches the backend (core/backend/options.go),
|
||||||
|
// so a value above math.MaxInt32 would silently wrap into a negative
|
||||||
|
// context size when no smaller ceiling exists.
|
||||||
|
if numCtx > math.MaxInt32 {
|
||||||
|
numCtx = math.MaxInt32
|
||||||
|
}
|
||||||
|
cfg.ContextSize = &numCtx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,4 +99,4 @@ func ollamaMessagesToOpenAI(messages []schema.OllamaMessage) []schema.Message {
|
|||||||
result = append(result, openAIMsg)
|
result = append(result, openAIMsg)
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
65
core/http/endpoints/ollama/helpers_internal_test.go
Normal file
65
core/http/endpoints/ollama/helpers_internal_test.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package ollama
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"github.com/mudler/LocalAI/core/config"
|
||||||
|
"github.com/mudler/LocalAI/core/schema"
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
// These specs pin the num_ctx handling for issue #11022. /api/chat and
|
||||||
|
// /api/generate share applyOllamaOptions, so exercising the helper covers both
|
||||||
|
// endpoints. A client-supplied options.num_ctx must not be able to *raise* the
|
||||||
|
// model/hardware-derived context ceiling already in cfg.ContextSize (that value
|
||||||
|
// drives KV-cache allocation, so an oversized request is a DoS), and it must
|
||||||
|
// stay int32-safe because ContextSize is cast to int32 before it reaches the
|
||||||
|
// backend, where an out-of-range value would silently wrap negative.
|
||||||
|
var _ = Describe("applyOllamaOptions num_ctx clamping (issue #11022)", func() {
|
||||||
|
It("caps num_ctx at math.MaxInt32 so the later int32 cast cannot wrap negative", func() {
|
||||||
|
cfg := &config.ModelConfig{}
|
||||||
|
applyOllamaOptions(&schema.OllamaOptions{NumCtx: math.MaxInt32 + 1}, cfg)
|
||||||
|
|
||||||
|
Expect(cfg.ContextSize).ToNot(BeNil())
|
||||||
|
Expect(*cfg.ContextSize).To(Equal(math.MaxInt32))
|
||||||
|
Expect(int32(*cfg.ContextSize)).To(BeNumerically(">", 0),
|
||||||
|
"capped context size must stay positive after the int32 cast")
|
||||||
|
})
|
||||||
|
|
||||||
|
It("does not let an oversized num_ctx raise an existing context ceiling", func() {
|
||||||
|
for _, ceiling := range []int{4096, 8192} {
|
||||||
|
existing := ceiling
|
||||||
|
cfg := &config.ModelConfig{ContextSize: &existing}
|
||||||
|
applyOllamaOptions(&schema.OllamaOptions{NumCtx: 2000000000}, cfg)
|
||||||
|
|
||||||
|
Expect(cfg.ContextSize).ToNot(BeNil())
|
||||||
|
Expect(*cfg.ContextSize).To(Equal(ceiling),
|
||||||
|
"an in-range but oversized num_ctx must be clamped to the server ceiling, not replace it")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
It("still honors a num_ctx smaller than the existing ceiling", func() {
|
||||||
|
existing := 8192
|
||||||
|
cfg := &config.ModelConfig{ContextSize: &existing}
|
||||||
|
applyOllamaOptions(&schema.OllamaOptions{NumCtx: 2048}, cfg)
|
||||||
|
|
||||||
|
Expect(cfg.ContextSize).ToNot(BeNil())
|
||||||
|
Expect(*cfg.ContextSize).To(Equal(2048))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("passes an in-range num_ctx through unchanged", func() {
|
||||||
|
cfg := &config.ModelConfig{}
|
||||||
|
applyOllamaOptions(&schema.OllamaOptions{NumCtx: 4096}, cfg)
|
||||||
|
|
||||||
|
Expect(cfg.ContextSize).ToNot(BeNil())
|
||||||
|
Expect(*cfg.ContextSize).To(Equal(4096))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("leaves ContextSize untouched when num_ctx is not set", func() {
|
||||||
|
cfg := &config.ModelConfig{}
|
||||||
|
applyOllamaOptions(&schema.OllamaOptions{}, cfg)
|
||||||
|
|
||||||
|
Expect(cfg.ContextSize).To(BeNil())
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user