Skip to content

Commit 84de7a6

Browse files
committed
feat(gallery): drop the redundant variant min_memory field
Variant.MinMemory was an authored override for when the live probe misreads a variant's footprint. It duplicated an existing field: probeEntryMemory already passes the entry's declared size: into EstimateModelMultiContext, whose cascade prefers that declared size over its own guesswork. Correcting size: on the referenced entry fixes the figure for every consumer rather than only for variant selection, so min_memory shadowed the right answer. A variant is now nothing but a name. Its effective size is exactly the probe result, and an unknown stays unknown: it survives the filter and ranks last. EffectiveMemory loses its error return along with the field. The authored string was the only thing that could fail to parse, so the error had no remaining source and was propagating dead nil-checks through SelectVariant, DescribeVariants and the pin warning. Selection behaviour is unchanged. The specs covering probe-derived sizing, ranking, filtering, the unknown-size path, pin recall, entry/variant metadata split and deep-copy isolation all survive; the three install specs that needed a definite size now declare it through the referenced entry's own size:, which exercises the documented escape hatch directly. gallery/index.yaml is untouched: no entry ever carried the key. Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
1 parent 77d01de commit 84de7a6

11 files changed

Lines changed: 126 additions & 277 deletions

.agents/adding-gallery-models.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,11 @@ Rules:
119119
and installs the largest survivor, falling back to the declaring entry's own
120120
build. Sizes are measured live from the weights and cached, so nothing has to
121121
be written down.
122-
- `min_memory` on a single variant (e.g. `min_memory: 20GiB`) overrides the
123-
measured size and suppresses the measurement for that variant. Use it only
124-
when you have measured a real load and know the estimate is wrong; most
125-
variants should not carry it.
122+
- A variant is nothing but a name; there is no per-variant memory field. When
123+
the measured size for a build is wrong, correct it on the referenced entry by
124+
setting that entry's own `size:` (e.g. `size: "20GiB"`). The estimator prefers
125+
a declared size over its own guesswork, so the fix applies everywhere the size
126+
is shown or compared rather than only to variant selection.
126127
127128
Users can override the automatic choice with `variant` on `POST /models/apply`,
128129
`local-ai models install --variant`, or the `install_model` MCP tool. See

core/gallery/describe_variants.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,7 @@ func DescribeVariants(models []*GalleryModel, entry *GalleryModel, env ResolveEn
6363

6464
views := make([]VariantView, 0, len(options))
6565
for _, o := range options {
66-
memory, known, err := o.EffectiveMemory()
67-
if err != nil {
68-
return nil, err
69-
}
66+
memory, known := o.EffectiveMemory()
7067
view := VariantView{
7168
Model: o.Variant.Model,
7269
Backend: o.Backend,

core/gallery/describe_variants_test.go

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -124,17 +124,6 @@ var _ = Describe("DescribeVariants", func() {
124124
Expect(byName(view, "qwen3-8b-gguf-q8").MemoryBytes).To(Equal(gib(9)))
125125
})
126126

127-
It("lets an authored min_memory win over the probe and skips that probe", func() {
128-
base.Variants = []gallery.Variant{{Model: "qwen3-8b-vllm-awq", MinMemory: "20GiB"}}
129-
130-
view, err := gallery.DescribeVariants(models, base, probing(gib(24), map[string]uint64{
131-
"qwen3-8b-vllm-awq": gib(2),
132-
}))
133-
Expect(err).ToNot(HaveOccurred())
134-
Expect(byName(view, "qwen3-8b-vllm-awq").MemoryBytes).To(Equal(gib(20)))
135-
Expect(probed).ToNot(ContainElement("qwen3-8b-vllm-awq"))
136-
})
137-
138127
It("reports a size it could not determine as unknown rather than as free", func() {
139128
// Zero on the wire means unknown. Rendering it as a zero-byte model
140129
// would advertise the largest download on offer as costless.

core/gallery/models.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,7 @@ func variantOptions(models []*GalleryModel, entry *GalleryModel, env ResolveEnv)
105105
}
106106

107107
option := VariantOption{Variant: v, Backend: target.Backend}
108-
// An authored figure short circuits the probe: the author already
109-
// answered the question the probe would spend a round trip asking.
110-
if v.MinMemory == "" && env.ProbeMemory != nil {
108+
if env.ProbeMemory != nil {
111109
option.ProbedMemory = env.ProbeMemory(target)
112110
}
113111
options = append(options, option)
@@ -206,7 +204,7 @@ func ResolveVariant(models []*GalleryModel, entry *GalleryModel, env ResolveEnv,
206204
// checks, but a silent bypass makes a later out-of-memory failure
207205
// impossible to trace back to the pin, so it is recorded loudly here.
208206
if pin != "" {
209-
if need, known, verr := selected.EffectiveMemory(); verr == nil && known && env.AvailableMemory < need {
207+
if need, known := selected.EffectiveMemory(); known && env.AvailableMemory < need {
210208
xlog.Warn("Pinned model variant declares more memory than this system reports; installing anyway because the pin overrides hardware resolution",
211209
"model", entry.Name, "variant", selected.Variant.Model, "required_memory", need, "available_memory", env.AvailableMemory)
212210
}

core/gallery/resolve_variant.go

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,10 @@ type VariantOption struct {
3131
// must stay installable on every host and for every client.
3232
IsBase bool
3333
// ProbedMemory is the footprint measured live from the referenced entry's
34-
// weights, in bytes. It is only consulted when the variant declares no
35-
// min_memory of its own, because a human who measured a real load knows
36-
// more than a pre-download estimate does.
34+
// weights, in bytes. It is the only source of a variant's size. An author
35+
// who needs to correct a bad estimate sets `size:` on the referenced entry,
36+
// which the estimator behind the probe already prefers over its own
37+
// guesswork, so the correction lands everywhere the size is used.
3738
//
3839
// Zero means the probe could not determine a size. That is an unknown, not
3940
// a zero requirement: a probe that cannot reach the network must never be
@@ -42,17 +43,12 @@ type VariantOption struct {
4243
}
4344

4445
// EffectiveMemory returns this option's memory requirement in bytes and whether
45-
// one is known at all: the authored figure when there is one, else the live
46-
// probe result, else nothing.
47-
func (o VariantOption) EffectiveMemory() (uint64, bool, error) {
48-
size, known, err := o.Variant.AuthoredMinMemory()
49-
if err != nil || known {
50-
return size, known, err
51-
}
46+
// one is known at all.
47+
func (o VariantOption) EffectiveMemory() (uint64, bool) {
5248
if o.ProbedMemory > 0 {
53-
return o.ProbedMemory, true, nil
49+
return o.ProbedMemory, true
5450
}
55-
return 0, false, nil
51+
return 0, false
5652
}
5753

5854
// ResolveEnv describes the host a variant is selected for.
@@ -69,13 +65,12 @@ type ResolveEnv struct {
6965
// caller with no view of the hardware.
7066
BackendCompatible func(backend string) bool
7167
// ProbeMemory measures how much memory a referenced gallery entry needs,
72-
// without downloading it. It is consulted only for variants that declare no
73-
// min_memory, and a zero result means "could not tell", never "needs
74-
// nothing".
68+
// without downloading it. A zero result means "could not tell", never
69+
// "needs nothing".
7570
//
7671
// It is a func field rather than a live network handle so specs can pin an
7772
// exact size, or an exact failure, without reaching the internet. A nil func
78-
// leaves every unauthored variant unknown, which selection already handles.
73+
// leaves every variant unknown, which selection already handles.
7974
//
8075
// SelectVariant never calls this: the install layer resolves every size into
8176
// VariantOption.ProbedMemory first, so the selector stays pure.
@@ -149,10 +144,7 @@ func SelectVariant(options []VariantOption, env ResolveEnv, pin string) (Variant
149144
continue
150145
}
151146

152-
memory, known, err := o.EffectiveMemory()
153-
if err != nil {
154-
return VariantSelection{}, err
155-
}
147+
memory, known := o.EffectiveMemory()
156148
if known && memory > env.AvailableMemory {
157149
reasons = append(reasons, fmt.Sprintf("%s needs %s of memory", o.Variant.Model, humanBytes(memory)))
158150
continue

0 commit comments

Comments
 (0)