mirror of
https://github.com/mudler/LocalAI.git
synced 2026-08-01 02:49:51 -04:00
Variant auto-selection filtered candidates by whether their backend can run on the host, then ranked the survivors by size alone. The backend never influenced the choice beyond that gate, so a Mac offered both an MLX build and a llama.cpp build kept neither filtered and installed whichever was larger, leaving the native accelerated runtime unused. The same held for CUDA against CPU on NVIDIA and ROCm against Vulkan on AMD. Rank by the host's backend preference between the fit tier and size: fit stays a filter, preference decides among the builds the host can equally hold, and size still separates builds on equally preferred runtimes. The preference data stays in one declarative table in pkg/system, now read by a prefix lookup instead of a switch, so adding a capability or reordering one host's runtimes is a one-line edit and the gallery's ranking code carries no per-backend branching. MLX joins the metal rule ahead of metal itself, which is inert for the existing alias-resolution consumer because no alias group holds a candidate named for mlx. An unrecognised backend, an unrecognised capability and an absent preference list all collapse to the previous size-only ordering rather than erroring or dropping candidates. Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
242 lines
6.6 KiB
Go
242 lines
6.6 KiB
Go
package system
|
|
|
|
import (
|
|
"os"
|
|
"runtime"
|
|
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
)
|
|
|
|
var _ = Describe("getSystemCapabilities", func() {
|
|
const eightGB = 8 * 1024 * 1024 * 1024
|
|
const twoGB = 2 * 1024 * 1024 * 1024
|
|
|
|
var (
|
|
origEnv string
|
|
origCuda12 bool
|
|
origCuda13 bool
|
|
)
|
|
|
|
BeforeEach(func() {
|
|
if runtime.GOOS == "darwin" {
|
|
Skip("darwin short-circuits before reaching CUDA logic")
|
|
}
|
|
|
|
origEnv = os.Getenv(capabilityEnv)
|
|
os.Unsetenv(capabilityEnv)
|
|
|
|
origCuda12 = cuda12DirExists
|
|
origCuda13 = cuda13DirExists
|
|
})
|
|
|
|
AfterEach(func() {
|
|
cuda12DirExists = origCuda12
|
|
cuda13DirExists = origCuda13
|
|
|
|
if origEnv != "" {
|
|
os.Setenv(capabilityEnv, origEnv)
|
|
}
|
|
})
|
|
|
|
type testCase struct {
|
|
gpuVendor string
|
|
vram uint64
|
|
cuda12 bool
|
|
cuda13 bool
|
|
wantCapability string
|
|
wantTokens []string
|
|
}
|
|
|
|
DescribeTable("capability detection",
|
|
func(tc testCase) {
|
|
cuda12DirExists = tc.cuda12
|
|
cuda13DirExists = tc.cuda13
|
|
|
|
s := &SystemState{
|
|
GPUVendor: tc.gpuVendor,
|
|
VRAM: tc.vram,
|
|
}
|
|
|
|
Expect(s.getSystemCapabilities()).To(Equal(tc.wantCapability))
|
|
Expect(s.BackendPreferenceTokens()).To(Equal(tc.wantTokens))
|
|
},
|
|
Entry("CUDA dir present but no GPU", testCase{
|
|
gpuVendor: "",
|
|
vram: 0,
|
|
cuda12: true,
|
|
cuda13: false,
|
|
wantCapability: "default",
|
|
wantTokens: []string{"cpu"},
|
|
}),
|
|
Entry("CUDA 12 with NVIDIA GPU", testCase{
|
|
gpuVendor: Nvidia,
|
|
vram: eightGB,
|
|
cuda12: true,
|
|
cuda13: false,
|
|
wantCapability: "nvidia-cuda-12",
|
|
wantTokens: []string{"cuda", "vulkan", "cpu"},
|
|
}),
|
|
Entry("CUDA 13 with NVIDIA GPU", testCase{
|
|
gpuVendor: Nvidia,
|
|
vram: eightGB,
|
|
cuda12: false,
|
|
cuda13: true,
|
|
wantCapability: "nvidia-cuda-13",
|
|
wantTokens: []string{"cuda", "vulkan", "cpu"},
|
|
}),
|
|
Entry("Both CUDA dirs with NVIDIA GPU prefers 13", testCase{
|
|
gpuVendor: Nvidia,
|
|
vram: eightGB,
|
|
cuda12: true,
|
|
cuda13: true,
|
|
wantCapability: "nvidia-cuda-13",
|
|
wantTokens: []string{"cuda", "vulkan", "cpu"},
|
|
}),
|
|
Entry("CUDA dir with AMD GPU ignored", testCase{
|
|
gpuVendor: AMD,
|
|
vram: eightGB,
|
|
cuda12: true,
|
|
cuda13: false,
|
|
wantCapability: "amd",
|
|
wantTokens: []string{"rocm", "hip", "vulkan", "cpu"},
|
|
}),
|
|
Entry("No CUDA dir and no GPU", testCase{
|
|
gpuVendor: "",
|
|
vram: 0,
|
|
cuda12: false,
|
|
cuda13: false,
|
|
wantCapability: "default",
|
|
wantTokens: []string{"cpu"},
|
|
}),
|
|
Entry("No CUDA dir with NVIDIA GPU", testCase{
|
|
gpuVendor: Nvidia,
|
|
vram: eightGB,
|
|
cuda12: false,
|
|
cuda13: false,
|
|
wantCapability: "nvidia",
|
|
wantTokens: []string{"cuda", "vulkan", "cpu"},
|
|
}),
|
|
Entry("CUDA dir with NVIDIA GPU but low VRAM", testCase{
|
|
gpuVendor: Nvidia,
|
|
vram: twoGB,
|
|
cuda12: true,
|
|
cuda13: false,
|
|
wantCapability: "default",
|
|
wantTokens: []string{"cpu"},
|
|
}),
|
|
)
|
|
})
|
|
|
|
var _ = Describe("BackendPreferenceTokens", func() {
|
|
var origEnv string
|
|
|
|
BeforeEach(func() {
|
|
origEnv = os.Getenv(capabilityEnv)
|
|
})
|
|
|
|
AfterEach(func() {
|
|
if origEnv != "" {
|
|
os.Setenv(capabilityEnv, origEnv)
|
|
} else {
|
|
os.Unsetenv(capabilityEnv)
|
|
}
|
|
})
|
|
|
|
tokensFor := func(capability string) []string {
|
|
GinkgoHelper()
|
|
Expect(os.Setenv(capabilityEnv, capability)).To(Succeed())
|
|
return (&SystemState{}).BackendPreferenceTokens()
|
|
}
|
|
|
|
It("ranks MLX above metal on apple silicon", func() {
|
|
// MLX is the native accelerated runtime there, so it must outrank a
|
|
// metal-enabled build of the portable engine.
|
|
Expect(tokensFor(metal)).To(Equal([]string{"mlx", "metal", "cpu"}))
|
|
})
|
|
|
|
It("keeps the vendor order every other capability already had", func() {
|
|
Expect(tokensFor("nvidia-cuda-12")).To(Equal([]string{"cuda", "vulkan", "cpu"}))
|
|
Expect(tokensFor(AMD)).To(Equal([]string{"rocm", "hip", "vulkan", "cpu"}))
|
|
Expect(tokensFor(Intel)).To(Equal([]string{"sycl", "intel", "cpu"}))
|
|
Expect(tokensFor(darwinX86)).To(Equal([]string{"darwin-x86", "cpu"}))
|
|
Expect(tokensFor(vulkan)).To(Equal([]string{"vulkan", "cpu"}))
|
|
})
|
|
|
|
It("degrades an unknown capability to cpu rather than to nothing", func() {
|
|
Expect(tokensFor("some-future-accelerator")).To(Equal([]string{"cpu"}))
|
|
})
|
|
|
|
It("matches a capability by prefix so a refined one keeps its vendor order", func() {
|
|
Expect(tokensFor("nvidia-l4t-cuda-13")).To(Equal(tokensFor("nvidia")))
|
|
})
|
|
|
|
It("hands out a copy so one caller cannot corrupt another's lookup", func() {
|
|
first := tokensFor("nvidia")
|
|
first[0] = "clobbered"
|
|
Expect(tokensFor("nvidia")[0]).To(Equal("cuda"))
|
|
})
|
|
})
|
|
|
|
var _ = Describe("CapabilityFilterDisabled", func() {
|
|
var origEnv string
|
|
|
|
BeforeEach(func() {
|
|
origEnv = os.Getenv(capabilityEnv)
|
|
})
|
|
|
|
AfterEach(func() {
|
|
if origEnv != "" {
|
|
os.Setenv(capabilityEnv, origEnv)
|
|
} else {
|
|
os.Unsetenv(capabilityEnv)
|
|
}
|
|
})
|
|
|
|
It("returns true when capability is set to disable", func() {
|
|
os.Setenv(capabilityEnv, "disable")
|
|
s := &SystemState{}
|
|
Expect(s.CapabilityFilterDisabled()).To(BeTrue())
|
|
})
|
|
|
|
It("returns false when capability is not set to disable", func() {
|
|
os.Setenv(capabilityEnv, "nvidia")
|
|
s := &SystemState{}
|
|
Expect(s.CapabilityFilterDisabled()).To(BeFalse())
|
|
})
|
|
|
|
It("makes IsBackendCompatible return true for all backends when disabled", func() {
|
|
os.Setenv(capabilityEnv, "disable")
|
|
s := &SystemState{}
|
|
Expect(s.IsBackendCompatible("cuda12-whisperx", "quay.io/nvidia-cuda-12")).To(BeTrue())
|
|
Expect(s.IsBackendCompatible("metal-whisperx", "quay.io/metal-darwin")).To(BeTrue())
|
|
Expect(s.IsBackendCompatible("intel-whisperx", "quay.io/intel-sycl")).To(BeTrue())
|
|
Expect(s.IsBackendCompatible("cpu-whisperx", "quay.io/cpu")).To(BeTrue())
|
|
})
|
|
})
|
|
|
|
var _ = Describe("DetectedCapability", func() {
|
|
var previous string
|
|
|
|
BeforeEach(func() {
|
|
previous = os.Getenv("LOCALAI_FORCE_META_BACKEND_CAPABILITY")
|
|
})
|
|
|
|
AfterEach(func() {
|
|
Expect(os.Setenv("LOCALAI_FORCE_META_BACKEND_CAPABILITY", previous)).To(Succeed())
|
|
})
|
|
|
|
It("returns the forced capability verbatim without map fallback", func() {
|
|
Expect(os.Setenv("LOCALAI_FORCE_META_BACKEND_CAPABILITY", "nvidia-cuda-12")).To(Succeed())
|
|
state := &SystemState{}
|
|
Expect(state.DetectedCapability()).To(Equal("nvidia-cuda-12"))
|
|
})
|
|
|
|
It("does not fall back to default when the capability is unusual", func() {
|
|
Expect(os.Setenv("LOCALAI_FORCE_META_BACKEND_CAPABILITY", "metal")).To(Succeed())
|
|
state := &SystemState{}
|
|
Expect(state.DetectedCapability()).To(Equal("metal"))
|
|
Expect(state.DetectedCapability()).NotTo(Equal("default"))
|
|
})
|
|
})
|