Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions core/config/model_artifact_inference.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,29 @@ import (
"github.com/mudler/LocalAI/pkg/modelartifacts"
)

// managedArtifactBackends is the set of backends that load a model from a
// snapshot *directory* (a HuggingFace repo consumed as a folder of weights,
// config, and tokenizer files). Only these backends may have a managed
// artifact *inferred* from a bare model reference: single-file backends such
// as llama.cpp or whisper would otherwise be handed the snapshot directory
// instead of the weight file and fail to load it. The importer relies on the
// same gate, so both paths agree on which backends auto-materialize.
var managedArtifactBackends = map[string]struct{}{
"transformers": {}, "huggingface-embeddings": {}, "sentencetransformers": {},
"transformers-musicgen": {}, "mamba": {}, "diffusers": {}, "qwen-asr": {},
"fish-speech": {}, "nemo": {}, "voxcpm": {}, "qwen-tts": {},
"liquid-audio": {}, "vllm": {}, "vllm-omni": {}, "sglang": {},
}

// IsManagedArtifactBackend reports whether backend consumes a model as a
// snapshot directory and is therefore eligible for inferred artifact
// materialization. An explicit artifacts: block bypasses this gate; single-file
// resolution handles the load path for single-file backends in that case.
func IsManagedArtifactBackend(backend string) bool {
_, ok := managedArtifactBackends[backend]
return ok
}

// PrimaryArtifactSpec returns the managed primary artifact to materialize for
// this config. The boolean return is false when the config should stay on the
// legacy path.
Expand All @@ -24,6 +47,12 @@ func (c ModelConfig) PrimaryArtifactSpec(modelsPath string) (modelartifacts.Spec
if len(c.DownloadFiles) > 0 {
return modelartifacts.Spec{}, false, false, nil
}
// Only directory-consuming backends may have an artifact inferred from a
// bare reference; single-file backends stay on the legacy download-to-file
// path so the backend receives the weight file itself, not its directory.
if !IsManagedArtifactBackend(c.Backend) {
return modelartifacts.Spec{}, false, false, nil
}

if modelsPath != "" {
for _, candidate := range []string{
Expand Down
53 changes: 53 additions & 0 deletions core/config/model_artifact_inference_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package config_test

import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"gopkg.in/yaml.v3"

"github.com/mudler/LocalAI/core/config"
)

var _ = Describe("PrimaryArtifactSpec backend gating", func() {
parse := func(doc string) config.ModelConfig {
var c config.ModelConfig
Expect(yaml.Unmarshal([]byte(doc), &c)).To(Succeed())
return c
}

It("does not infer a managed artifact for a single-file backend", func() {
// llama.cpp consumes a single GGUF file, not a snapshot directory.
// A bare HuggingFace file reference must stay on the legacy
// download-to-file path so the backend receives the file itself.
c := parse("backend: llama-cpp\nparameters: {model: huggingface://owner/repo/model.gguf}\n")
_, inferred, managed, err := c.PrimaryArtifactSpec("/models")
Expect(err).NotTo(HaveOccurred())
Expect(managed).To(BeFalse())
Expect(inferred).To(BeFalse())
})

It("does not infer a managed artifact for a bare repo on a single-file backend", func() {
c := parse("backend: llama-cpp\nparameters: {model: owner/repo}\n")
_, _, managed, err := c.PrimaryArtifactSpec("/models")
Expect(err).NotTo(HaveOccurred())
Expect(managed).To(BeFalse())
})

It("infers a managed artifact for a directory-consuming backend", func() {
c := parse("backend: transformers\nparameters: {model: huggingface://owner/repo/model.safetensors}\n")
spec, inferred, managed, err := c.PrimaryArtifactSpec("/models")
Expect(err).NotTo(HaveOccurred())
Expect(managed).To(BeTrue())
Expect(inferred).To(BeTrue())
Expect(spec.Source.Repo).To(Equal("owner/repo"))
})

It("keeps explicit artifacts managed even on a single-file backend", func() {
// An explicitly declared artifacts: block is a deliberate choice;
// single-file resolution (PrimaryFile) handles the load path.
c := parse("backend: llama-cpp\nartifacts:\n - source: {type: huggingface, repo: owner/repo}\nparameters: {model: owner/repo}\n")
_, _, managed, err := c.PrimaryArtifactSpec("/models")
Expect(err).NotTo(HaveOccurred())
Expect(managed).To(BeTrue())
})
})
9 changes: 1 addition & 8 deletions core/gallery/importers/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,6 @@ import (
"gopkg.in/yaml.v3"
)

var managedArtifactBackends = map[string]struct{}{
"transformers": {}, "huggingface-embeddings": {}, "sentencetransformers": {},
"transformers-musicgen": {}, "mamba": {}, "diffusers": {}, "qwen-asr": {},
"fish-speech": {}, "nemo": {}, "voxcpm": {}, "qwen-tts": {},
"liquid-audio": {}, "vllm": {}, "vllm-omni": {}, "sglang": {},
}

// AttachPrimaryArtifact adds the controller-managed source only when the
// importer selected the same repository and a migrated backend.
func AttachPrimaryArtifact(model gallery.ModelConfig, details Details) (gallery.ModelConfig, error) {
Expand All @@ -29,7 +22,7 @@ func AttachPrimaryArtifact(model gallery.ModelConfig, details Details) (gallery.
if err := yaml.Unmarshal([]byte(model.ConfigFile), &cfg); err != nil {
return gallery.ModelConfig{}, err
}
if _, supported := managedArtifactBackends[cfg.Backend]; !supported {
if !config.IsManagedArtifactBackend(cfg.Backend) {
return model, nil
}
if len(cfg.Artifacts) != 0 || cfg.Model != details.HuggingFace.ModelID {
Expand Down
Loading