|
|
package gallery |
|
|
|
|
|
import ( |
|
|
"context" |
|
|
"encoding/json" |
|
|
"os" |
|
|
"path/filepath" |
|
|
"runtime" |
|
|
|
|
|
"github.com/mudler/LocalAI/core/config" |
|
|
"github.com/mudler/LocalAI/pkg/model" |
|
|
"github.com/mudler/LocalAI/pkg/system" |
|
|
. "github.com/onsi/ginkgo/v2" |
|
|
. "github.com/onsi/gomega" |
|
|
"gopkg.in/yaml.v2" |
|
|
) |
|
|
|
|
|
const ( |
|
|
testImage = "quay.io/mudler/tests:localai-backend-test" |
|
|
) |
|
|
|
|
|
var _ = Describe("Runtime capability-based backend selection", func() { |
|
|
var tempDir string |
|
|
|
|
|
BeforeEach(func() { |
|
|
var err error |
|
|
tempDir, err = os.MkdirTemp("", "gallery-caps-*") |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
}) |
|
|
|
|
|
AfterEach(func() { |
|
|
os.RemoveAll(tempDir) |
|
|
}) |
|
|
|
|
|
It("ListSystemBackends prefers optimal alias candidate", func() { |
|
|
|
|
|
must := func(err error) { Expect(err).NotTo(HaveOccurred()) } |
|
|
|
|
|
cpuDir := filepath.Join(tempDir, "cpu-llama-cpp") |
|
|
must(os.MkdirAll(cpuDir, 0o750)) |
|
|
cpuMeta := &BackendMetadata{Alias: "llama-cpp", Name: "cpu-llama-cpp"} |
|
|
b, _ := json.Marshal(cpuMeta) |
|
|
must(os.WriteFile(filepath.Join(cpuDir, "metadata.json"), b, 0o644)) |
|
|
must(os.WriteFile(filepath.Join(cpuDir, "run.sh"), []byte(""), 0o755)) |
|
|
|
|
|
cudaDir := filepath.Join(tempDir, "cuda12-llama-cpp") |
|
|
must(os.MkdirAll(cudaDir, 0o750)) |
|
|
cudaMeta := &BackendMetadata{Alias: "llama-cpp", Name: "cuda12-llama-cpp"} |
|
|
b, _ = json.Marshal(cudaMeta) |
|
|
must(os.WriteFile(filepath.Join(cudaDir, "metadata.json"), b, 0o644)) |
|
|
must(os.WriteFile(filepath.Join(cudaDir, "run.sh"), []byte(""), 0o755)) |
|
|
|
|
|
|
|
|
sysDefault, err := system.GetSystemState( |
|
|
system.WithBackendPath(tempDir), |
|
|
) |
|
|
must(err) |
|
|
sysDefault.GPUVendor = "" |
|
|
backs, err := ListSystemBackends(sysDefault) |
|
|
must(err) |
|
|
aliasBack, ok := backs.Get("llama-cpp") |
|
|
Expect(ok).To(BeTrue()) |
|
|
Expect(aliasBack.RunFile).To(Equal(filepath.Join(cpuDir, "run.sh"))) |
|
|
|
|
|
_, ok = backs.Get("cpu-llama-cpp") |
|
|
Expect(ok).To(BeTrue()) |
|
|
_, ok = backs.Get("cuda12-llama-cpp") |
|
|
Expect(ok).To(BeTrue()) |
|
|
|
|
|
|
|
|
|
|
|
os.Setenv("LOCALAI_FORCE_META_BACKEND_CAPABILITY", "nvidia") |
|
|
defer os.Unsetenv("LOCALAI_FORCE_META_BACKEND_CAPABILITY") |
|
|
|
|
|
sysNvidia, err := system.GetSystemState( |
|
|
system.WithBackendPath(tempDir), |
|
|
) |
|
|
must(err) |
|
|
sysNvidia.GPUVendor = "nvidia" |
|
|
sysNvidia.VRAM = 8 * 1024 * 1024 * 1024 |
|
|
backs, err = ListSystemBackends(sysNvidia) |
|
|
must(err) |
|
|
aliasBack, ok = backs.Get("llama-cpp") |
|
|
Expect(ok).To(BeTrue()) |
|
|
Expect(aliasBack.RunFile).To(Equal(filepath.Join(cudaDir, "run.sh"))) |
|
|
}) |
|
|
}) |
|
|
|
|
|
var _ = Describe("Gallery Backends", func() { |
|
|
var ( |
|
|
tempDir string |
|
|
galleries []config.Gallery |
|
|
ml *model.ModelLoader |
|
|
systemState *system.SystemState |
|
|
) |
|
|
|
|
|
BeforeEach(func() { |
|
|
var err error |
|
|
tempDir, err = os.MkdirTemp("", "gallery-test-*") |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
|
|
|
|
|
|
galleries = []config.Gallery{ |
|
|
{ |
|
|
Name: "test-gallery", |
|
|
URL: "https://gist.githubusercontent.com/mudler/71d5376bc2aa168873fa519fa9f4bd56/raw/0557f9c640c159fa8e4eab29e8d98df6a3d6e80f/backend-gallery.yaml", |
|
|
}, |
|
|
} |
|
|
systemState, err = system.GetSystemState(system.WithBackendPath(tempDir)) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
ml = model.NewModelLoader(systemState) |
|
|
}) |
|
|
|
|
|
AfterEach(func() { |
|
|
os.RemoveAll(tempDir) |
|
|
}) |
|
|
|
|
|
Describe("InstallBackendFromGallery", func() { |
|
|
It("should return error when backend is not found", func() { |
|
|
err := InstallBackendFromGallery(context.TODO(), galleries, systemState, ml, "non-existent", nil, true) |
|
|
Expect(err).To(HaveOccurred()) |
|
|
Expect(err.Error()).To(ContainSubstring("no backend found with name \"non-existent\"")) |
|
|
}) |
|
|
|
|
|
It("should install backend from gallery", func() { |
|
|
err := InstallBackendFromGallery(context.TODO(), galleries, systemState, ml, "test-backend", nil, true) |
|
|
Expect(err).ToNot(HaveOccurred()) |
|
|
Expect(filepath.Join(tempDir, "test-backend", "run.sh")).To(BeARegularFile()) |
|
|
}) |
|
|
}) |
|
|
|
|
|
Describe("Meta Backends", func() { |
|
|
It("should identify meta backends correctly", func() { |
|
|
metaBackend := &GalleryBackend{ |
|
|
Metadata: Metadata{ |
|
|
Name: "meta-backend", |
|
|
}, |
|
|
CapabilitiesMap: map[string]string{ |
|
|
"nvidia": "nvidia-backend", |
|
|
"amd": "amd-backend", |
|
|
"intel": "intel-backend", |
|
|
}, |
|
|
} |
|
|
|
|
|
Expect(metaBackend.IsMeta()).To(BeTrue()) |
|
|
|
|
|
regularBackend := &GalleryBackend{ |
|
|
Metadata: Metadata{ |
|
|
Name: "regular-backend", |
|
|
}, |
|
|
URI: testImage, |
|
|
} |
|
|
|
|
|
Expect(regularBackend.IsMeta()).To(BeFalse()) |
|
|
|
|
|
emptyMetaBackend := &GalleryBackend{ |
|
|
Metadata: Metadata{ |
|
|
Name: "empty-meta-backend", |
|
|
}, |
|
|
CapabilitiesMap: map[string]string{}, |
|
|
} |
|
|
|
|
|
Expect(emptyMetaBackend.IsMeta()).To(BeFalse()) |
|
|
|
|
|
nilMetaBackend := &GalleryBackend{ |
|
|
Metadata: Metadata{ |
|
|
Name: "nil-meta-backend", |
|
|
}, |
|
|
CapabilitiesMap: nil, |
|
|
} |
|
|
|
|
|
Expect(nilMetaBackend.IsMeta()).To(BeFalse()) |
|
|
}) |
|
|
|
|
|
It("should check IsCompatibleWith correctly for meta backends", func() { |
|
|
metaBackend := &GalleryBackend{ |
|
|
Metadata: Metadata{ |
|
|
Name: "meta-backend", |
|
|
}, |
|
|
CapabilitiesMap: map[string]string{ |
|
|
"nvidia": "nvidia-backend", |
|
|
"amd": "amd-backend", |
|
|
"default": "default-backend", |
|
|
}, |
|
|
} |
|
|
|
|
|
|
|
|
Expect(metaBackend.IsCompatibleWith(nil)).To(BeTrue()) |
|
|
|
|
|
|
|
|
nvidiaState := &system.SystemState{GPUVendor: "nvidia", VRAM: 8 * 1024 * 1024 * 1024} |
|
|
Expect(metaBackend.IsCompatibleWith(nvidiaState)).To(BeTrue()) |
|
|
|
|
|
|
|
|
defaultState := &system.SystemState{} |
|
|
Expect(metaBackend.IsCompatibleWith(defaultState)).To(BeTrue()) |
|
|
}) |
|
|
|
|
|
Describe("IsCompatibleWith for concrete backends", func() { |
|
|
Context("CPU backends", func() { |
|
|
It("should be compatible on all systems", func() { |
|
|
cpuBackend := &GalleryBackend{ |
|
|
Metadata: Metadata{ |
|
|
Name: "cpu-llama-cpp", |
|
|
}, |
|
|
URI: "quay.io/go-skynet/local-ai-backends:latest-cpu-llama-cpp", |
|
|
} |
|
|
Expect(cpuBackend.IsCompatibleWith(&system.SystemState{})).To(BeTrue()) |
|
|
Expect(cpuBackend.IsCompatibleWith(&system.SystemState{GPUVendor: system.Nvidia, VRAM: 8 * 1024 * 1024 * 1024})).To(BeTrue()) |
|
|
Expect(cpuBackend.IsCompatibleWith(&system.SystemState{GPUVendor: system.AMD, VRAM: 8 * 1024 * 1024 * 1024})).To(BeTrue()) |
|
|
}) |
|
|
}) |
|
|
|
|
|
Context("Darwin/Metal backends", func() { |
|
|
When("running on darwin", func() { |
|
|
BeforeEach(func() { |
|
|
if runtime.GOOS != "darwin" { |
|
|
Skip("Skipping darwin-specific tests on non-darwin system") |
|
|
} |
|
|
}) |
|
|
|
|
|
It("should be compatible for MLX backend", func() { |
|
|
mlxBackend := &GalleryBackend{ |
|
|
Metadata: Metadata{ |
|
|
Name: "mlx", |
|
|
}, |
|
|
URI: "quay.io/go-skynet/local-ai-backends:latest-metal-darwin-arm64-mlx", |
|
|
} |
|
|
Expect(mlxBackend.IsCompatibleWith(&system.SystemState{})).To(BeTrue()) |
|
|
}) |
|
|
|
|
|
It("should be compatible for metal-llama-cpp backend", func() { |
|
|
metalBackend := &GalleryBackend{ |
|
|
Metadata: Metadata{ |
|
|
Name: "metal-llama-cpp", |
|
|
}, |
|
|
URI: "quay.io/go-skynet/local-ai-backends:latest-metal-darwin-arm64-llama-cpp", |
|
|
} |
|
|
Expect(metalBackend.IsCompatibleWith(&system.SystemState{})).To(BeTrue()) |
|
|
}) |
|
|
}) |
|
|
|
|
|
When("running on non-darwin", func() { |
|
|
BeforeEach(func() { |
|
|
if runtime.GOOS == "darwin" { |
|
|
Skip("Skipping non-darwin-specific tests on darwin system") |
|
|
} |
|
|
}) |
|
|
|
|
|
It("should NOT be compatible for MLX backend", func() { |
|
|
mlxBackend := &GalleryBackend{ |
|
|
Metadata: Metadata{ |
|
|
Name: "mlx", |
|
|
}, |
|
|
URI: "quay.io/go-skynet/local-ai-backends:latest-metal-darwin-arm64-mlx", |
|
|
} |
|
|
Expect(mlxBackend.IsCompatibleWith(&system.SystemState{})).To(BeFalse()) |
|
|
}) |
|
|
|
|
|
It("should NOT be compatible for metal-llama-cpp backend", func() { |
|
|
metalBackend := &GalleryBackend{ |
|
|
Metadata: Metadata{ |
|
|
Name: "metal-llama-cpp", |
|
|
}, |
|
|
URI: "quay.io/go-skynet/local-ai-backends:latest-metal-darwin-arm64-llama-cpp", |
|
|
} |
|
|
Expect(metalBackend.IsCompatibleWith(&system.SystemState{})).To(BeFalse()) |
|
|
}) |
|
|
}) |
|
|
}) |
|
|
|
|
|
Context("NVIDIA/CUDA backends", func() { |
|
|
When("running on non-darwin", func() { |
|
|
BeforeEach(func() { |
|
|
if runtime.GOOS == "darwin" { |
|
|
Skip("Skipping CUDA tests on darwin system") |
|
|
} |
|
|
}) |
|
|
|
|
|
It("should NOT be compatible without nvidia GPU", func() { |
|
|
cudaBackend := &GalleryBackend{ |
|
|
Metadata: Metadata{ |
|
|
Name: "cuda12-llama-cpp", |
|
|
}, |
|
|
URI: "quay.io/go-skynet/local-ai-backends:latest-gpu-nvidia-cuda-12-llama-cpp", |
|
|
} |
|
|
Expect(cudaBackend.IsCompatibleWith(&system.SystemState{})).To(BeFalse()) |
|
|
Expect(cudaBackend.IsCompatibleWith(&system.SystemState{GPUVendor: system.AMD, VRAM: 8 * 1024 * 1024 * 1024})).To(BeFalse()) |
|
|
}) |
|
|
|
|
|
It("should be compatible with nvidia GPU", func() { |
|
|
cudaBackend := &GalleryBackend{ |
|
|
Metadata: Metadata{ |
|
|
Name: "cuda12-llama-cpp", |
|
|
}, |
|
|
URI: "quay.io/go-skynet/local-ai-backends:latest-gpu-nvidia-cuda-12-llama-cpp", |
|
|
} |
|
|
Expect(cudaBackend.IsCompatibleWith(&system.SystemState{GPUVendor: system.Nvidia, VRAM: 8 * 1024 * 1024 * 1024})).To(BeTrue()) |
|
|
}) |
|
|
|
|
|
It("should be compatible with cuda13 backend on nvidia GPU", func() { |
|
|
cuda13Backend := &GalleryBackend{ |
|
|
Metadata: Metadata{ |
|
|
Name: "cuda13-llama-cpp", |
|
|
}, |
|
|
URI: "quay.io/go-skynet/local-ai-backends:latest-gpu-nvidia-cuda-13-llama-cpp", |
|
|
} |
|
|
Expect(cuda13Backend.IsCompatibleWith(&system.SystemState{GPUVendor: system.Nvidia, VRAM: 8 * 1024 * 1024 * 1024})).To(BeTrue()) |
|
|
}) |
|
|
}) |
|
|
}) |
|
|
|
|
|
Context("AMD/ROCm backends", func() { |
|
|
When("running on non-darwin", func() { |
|
|
BeforeEach(func() { |
|
|
if runtime.GOOS == "darwin" { |
|
|
Skip("Skipping AMD/ROCm tests on darwin system") |
|
|
} |
|
|
}) |
|
|
|
|
|
It("should NOT be compatible without AMD GPU", func() { |
|
|
rocmBackend := &GalleryBackend{ |
|
|
Metadata: Metadata{ |
|
|
Name: "rocm-llama-cpp", |
|
|
}, |
|
|
URI: "quay.io/go-skynet/local-ai-backends:latest-gpu-rocm-hipblas-llama-cpp", |
|
|
} |
|
|
Expect(rocmBackend.IsCompatibleWith(&system.SystemState{})).To(BeFalse()) |
|
|
Expect(rocmBackend.IsCompatibleWith(&system.SystemState{GPUVendor: system.Nvidia, VRAM: 8 * 1024 * 1024 * 1024})).To(BeFalse()) |
|
|
}) |
|
|
|
|
|
It("should be compatible with AMD GPU", func() { |
|
|
rocmBackend := &GalleryBackend{ |
|
|
Metadata: Metadata{ |
|
|
Name: "rocm-llama-cpp", |
|
|
}, |
|
|
URI: "quay.io/go-skynet/local-ai-backends:latest-gpu-rocm-hipblas-llama-cpp", |
|
|
} |
|
|
Expect(rocmBackend.IsCompatibleWith(&system.SystemState{GPUVendor: system.AMD, VRAM: 8 * 1024 * 1024 * 1024})).To(BeTrue()) |
|
|
}) |
|
|
|
|
|
It("should be compatible with hipblas backend on AMD GPU", func() { |
|
|
hipBackend := &GalleryBackend{ |
|
|
Metadata: Metadata{ |
|
|
Name: "hip-llama-cpp", |
|
|
}, |
|
|
URI: "quay.io/go-skynet/local-ai-backends:latest-gpu-hip-llama-cpp", |
|
|
} |
|
|
Expect(hipBackend.IsCompatibleWith(&system.SystemState{GPUVendor: system.AMD, VRAM: 8 * 1024 * 1024 * 1024})).To(BeTrue()) |
|
|
}) |
|
|
}) |
|
|
}) |
|
|
|
|
|
Context("Intel/SYCL backends", func() { |
|
|
When("running on non-darwin", func() { |
|
|
BeforeEach(func() { |
|
|
if runtime.GOOS == "darwin" { |
|
|
Skip("Skipping Intel/SYCL tests on darwin system") |
|
|
} |
|
|
}) |
|
|
|
|
|
It("should NOT be compatible without Intel GPU", func() { |
|
|
intelBackend := &GalleryBackend{ |
|
|
Metadata: Metadata{ |
|
|
Name: "intel-sycl-f16-llama-cpp", |
|
|
}, |
|
|
URI: "quay.io/go-skynet/local-ai-backends:latest-gpu-intel-sycl-f16-llama-cpp", |
|
|
} |
|
|
Expect(intelBackend.IsCompatibleWith(&system.SystemState{})).To(BeFalse()) |
|
|
Expect(intelBackend.IsCompatibleWith(&system.SystemState{GPUVendor: system.Nvidia, VRAM: 8 * 1024 * 1024 * 1024})).To(BeFalse()) |
|
|
}) |
|
|
|
|
|
It("should be compatible with Intel GPU", func() { |
|
|
intelBackend := &GalleryBackend{ |
|
|
Metadata: Metadata{ |
|
|
Name: "intel-sycl-f16-llama-cpp", |
|
|
}, |
|
|
URI: "quay.io/go-skynet/local-ai-backends:latest-gpu-intel-sycl-f16-llama-cpp", |
|
|
} |
|
|
Expect(intelBackend.IsCompatibleWith(&system.SystemState{GPUVendor: system.Intel, VRAM: 8 * 1024 * 1024 * 1024})).To(BeTrue()) |
|
|
}) |
|
|
|
|
|
It("should be compatible with intel-sycl-f32 backend on Intel GPU", func() { |
|
|
intelF32Backend := &GalleryBackend{ |
|
|
Metadata: Metadata{ |
|
|
Name: "intel-sycl-f32-llama-cpp", |
|
|
}, |
|
|
URI: "quay.io/go-skynet/local-ai-backends:latest-gpu-intel-sycl-f32-llama-cpp", |
|
|
} |
|
|
Expect(intelF32Backend.IsCompatibleWith(&system.SystemState{GPUVendor: system.Intel, VRAM: 8 * 1024 * 1024 * 1024})).To(BeTrue()) |
|
|
}) |
|
|
|
|
|
It("should be compatible with intel-transformers backend on Intel GPU", func() { |
|
|
intelTransformersBackend := &GalleryBackend{ |
|
|
Metadata: Metadata{ |
|
|
Name: "intel-transformers", |
|
|
}, |
|
|
URI: "quay.io/go-skynet/local-ai-backends:latest-intel-transformers", |
|
|
} |
|
|
Expect(intelTransformersBackend.IsCompatibleWith(&system.SystemState{GPUVendor: system.Intel, VRAM: 8 * 1024 * 1024 * 1024})).To(BeTrue()) |
|
|
}) |
|
|
}) |
|
|
}) |
|
|
|
|
|
Context("Vulkan backends", func() { |
|
|
It("should be compatible on CPU-only systems", func() { |
|
|
|
|
|
|
|
|
vulkanBackend := &GalleryBackend{ |
|
|
Metadata: Metadata{ |
|
|
Name: "vulkan-llama-cpp", |
|
|
}, |
|
|
URI: "quay.io/go-skynet/local-ai-backends:latest-gpu-vulkan-llama-cpp", |
|
|
} |
|
|
|
|
|
Expect(vulkanBackend.IsCompatibleWith(&system.SystemState{})).To(BeTrue()) |
|
|
}) |
|
|
}) |
|
|
}) |
|
|
|
|
|
It("should find best backend from meta based on system capabilities", func() { |
|
|
|
|
|
metaBackend := &GalleryBackend{ |
|
|
Metadata: Metadata{ |
|
|
Name: "meta-backend", |
|
|
}, |
|
|
CapabilitiesMap: map[string]string{ |
|
|
"nvidia": "nvidia-backend", |
|
|
"amd": "amd-backend", |
|
|
"intel": "intel-backend", |
|
|
"metal": "metal-backend", |
|
|
"default": "default-backend", |
|
|
}, |
|
|
} |
|
|
|
|
|
nvidiaBackend := &GalleryBackend{ |
|
|
Metadata: Metadata{ |
|
|
Name: "nvidia-backend", |
|
|
}, |
|
|
URI: testImage, |
|
|
} |
|
|
|
|
|
amdBackend := &GalleryBackend{ |
|
|
Metadata: Metadata{ |
|
|
Name: "amd-backend", |
|
|
}, |
|
|
URI: testImage, |
|
|
} |
|
|
|
|
|
metalBackend := &GalleryBackend{ |
|
|
Metadata: Metadata{ |
|
|
Name: "metal-backend", |
|
|
}, |
|
|
URI: testImage, |
|
|
} |
|
|
|
|
|
defaultBackend := &GalleryBackend{ |
|
|
Metadata: Metadata{ |
|
|
Name: "default-backend", |
|
|
}, |
|
|
URI: testImage, |
|
|
} |
|
|
|
|
|
backends := GalleryElements[*GalleryBackend]{nvidiaBackend, amdBackend, metalBackend, defaultBackend} |
|
|
|
|
|
if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" { |
|
|
metal := &system.SystemState{} |
|
|
bestBackend := metaBackend.FindBestBackendFromMeta(metal, backends) |
|
|
Expect(bestBackend).To(Equal(metalBackend)) |
|
|
|
|
|
} else { |
|
|
|
|
|
nvidiaSystemState := &system.SystemState{GPUVendor: "nvidia", VRAM: 1000000000000} |
|
|
bestBackend := metaBackend.FindBestBackendFromMeta(nvidiaSystemState, backends) |
|
|
Expect(bestBackend).To(Equal(nvidiaBackend)) |
|
|
|
|
|
|
|
|
amdSystemState := &system.SystemState{GPUVendor: "amd", VRAM: 1000000000000} |
|
|
bestBackend = metaBackend.FindBestBackendFromMeta(amdSystemState, backends) |
|
|
Expect(bestBackend).To(Equal(amdBackend)) |
|
|
|
|
|
|
|
|
defaultSystemState := &system.SystemState{GPUVendor: "amd"} |
|
|
bestBackend = metaBackend.FindBestBackendFromMeta(defaultSystemState, backends) |
|
|
Expect(bestBackend).To(Equal(defaultBackend)) |
|
|
|
|
|
|
|
|
defaultSystemState = &system.SystemState{GPUVendor: "default"} |
|
|
bestBackend = metaBackend.FindBestBackendFromMeta(defaultSystemState, backends) |
|
|
Expect(bestBackend).To(Equal(defaultBackend)) |
|
|
|
|
|
backends = GalleryElements[*GalleryBackend]{nvidiaBackend, amdBackend, metalBackend} |
|
|
|
|
|
unsupportedSystemState := &system.SystemState{GPUVendor: "unsupported"} |
|
|
bestBackend = metaBackend.FindBestBackendFromMeta(unsupportedSystemState, backends) |
|
|
Expect(bestBackend).To(BeNil()) |
|
|
} |
|
|
}) |
|
|
|
|
|
It("should handle meta backend deletion correctly", func() { |
|
|
if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" { |
|
|
Skip("Skipping test on darwin/arm64") |
|
|
} |
|
|
|
|
|
metaBackend := &GalleryBackend{ |
|
|
Metadata: Metadata{ |
|
|
Name: "meta-backend", |
|
|
}, |
|
|
CapabilitiesMap: map[string]string{ |
|
|
"nvidia": "nvidia-backend", |
|
|
"amd": "amd-backend", |
|
|
"intel": "intel-backend", |
|
|
}, |
|
|
} |
|
|
|
|
|
nvidiaBackend := &GalleryBackend{ |
|
|
Metadata: Metadata{ |
|
|
Name: "nvidia-backend", |
|
|
}, |
|
|
URI: testImage, |
|
|
} |
|
|
|
|
|
amdBackend := &GalleryBackend{ |
|
|
Metadata: Metadata{ |
|
|
Name: "amd-backend", |
|
|
}, |
|
|
URI: testImage, |
|
|
} |
|
|
|
|
|
gallery := config.Gallery{ |
|
|
Name: "test-gallery", |
|
|
URL: "file://" + filepath.Join(tempDir, "backend-gallery.yaml"), |
|
|
} |
|
|
|
|
|
galleryBackend := GalleryBackends{amdBackend, nvidiaBackend, metaBackend} |
|
|
|
|
|
dat, err := yaml.Marshal(galleryBackend) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
err = os.WriteFile(filepath.Join(tempDir, "backend-gallery.yaml"), dat, 0644) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
|
|
|
|
|
|
nvidiaSystemState := &system.SystemState{ |
|
|
GPUVendor: "nvidia", |
|
|
VRAM: 1000000000000, |
|
|
Backend: system.Backend{BackendsPath: tempDir}, |
|
|
} |
|
|
err = InstallBackendFromGallery(context.TODO(), []config.Gallery{gallery}, nvidiaSystemState, ml, "meta-backend", nil, true) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
|
|
|
metaBackendPath := filepath.Join(tempDir, "meta-backend") |
|
|
Expect(metaBackendPath).To(BeADirectory()) |
|
|
|
|
|
concreteBackendPath := filepath.Join(tempDir, "nvidia-backend") |
|
|
Expect(concreteBackendPath).To(BeADirectory()) |
|
|
|
|
|
systemState, err := system.GetSystemState( |
|
|
system.WithBackendPath(tempDir), |
|
|
) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
|
|
|
allBackends, err := ListSystemBackends(systemState) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
Expect(allBackends).To(HaveKey("meta-backend")) |
|
|
Expect(allBackends).To(HaveKey("nvidia-backend")) |
|
|
|
|
|
|
|
|
err = DeleteBackendFromSystem(systemState, "meta-backend") |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
|
|
|
|
|
|
Expect(metaBackendPath).NotTo(BeADirectory()) |
|
|
|
|
|
|
|
|
Expect(concreteBackendPath).NotTo(BeADirectory()) |
|
|
}) |
|
|
|
|
|
It("should handle meta backend deletion correctly with aliases", func() { |
|
|
if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" { |
|
|
Skip("Skipping test on darwin/arm64") |
|
|
} |
|
|
metaBackend := &GalleryBackend{ |
|
|
Metadata: Metadata{ |
|
|
Name: "meta-backend", |
|
|
}, |
|
|
Alias: "backend-alias", |
|
|
CapabilitiesMap: map[string]string{ |
|
|
"nvidia": "nvidia-backend", |
|
|
"amd": "amd-backend", |
|
|
"intel": "intel-backend", |
|
|
}, |
|
|
} |
|
|
|
|
|
nvidiaBackend := &GalleryBackend{ |
|
|
Metadata: Metadata{ |
|
|
Name: "nvidia-backend", |
|
|
}, |
|
|
Alias: "backend-alias", |
|
|
URI: testImage, |
|
|
} |
|
|
|
|
|
amdBackend := &GalleryBackend{ |
|
|
Metadata: Metadata{ |
|
|
Name: "amd-backend", |
|
|
}, |
|
|
Alias: "backend-alias", |
|
|
URI: testImage, |
|
|
} |
|
|
|
|
|
gallery := config.Gallery{ |
|
|
Name: "test-gallery", |
|
|
URL: "file://" + filepath.Join(tempDir, "backend-gallery.yaml"), |
|
|
} |
|
|
|
|
|
galleryBackend := GalleryBackends{amdBackend, nvidiaBackend, metaBackend} |
|
|
|
|
|
dat, err := yaml.Marshal(galleryBackend) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
err = os.WriteFile(filepath.Join(tempDir, "backend-gallery.yaml"), dat, 0644) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
|
|
|
|
|
|
nvidiaSystemState := &system.SystemState{ |
|
|
GPUVendor: "nvidia", |
|
|
VRAM: 1000000000000, |
|
|
Backend: system.Backend{BackendsPath: tempDir}, |
|
|
} |
|
|
err = InstallBackendFromGallery(context.TODO(), []config.Gallery{gallery}, nvidiaSystemState, ml, "meta-backend", nil, true) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
|
|
|
metaBackendPath := filepath.Join(tempDir, "meta-backend") |
|
|
Expect(metaBackendPath).To(BeADirectory()) |
|
|
|
|
|
concreteBackendPath := filepath.Join(tempDir, "nvidia-backend") |
|
|
Expect(concreteBackendPath).To(BeADirectory()) |
|
|
|
|
|
systemState, err := system.GetSystemState( |
|
|
system.WithBackendPath(tempDir), |
|
|
) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
|
|
|
allBackends, err := ListSystemBackends(systemState) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
Expect(allBackends).To(HaveKey("meta-backend")) |
|
|
Expect(allBackends).To(HaveKey("nvidia-backend")) |
|
|
mback, exists := allBackends.Get("meta-backend") |
|
|
Expect(exists).To(BeTrue()) |
|
|
Expect(mback.IsMeta).To(BeTrue()) |
|
|
Expect(mback.Metadata.MetaBackendFor).To(Equal("nvidia-backend")) |
|
|
|
|
|
|
|
|
err = DeleteBackendFromSystem(systemState, "meta-backend") |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
|
|
|
|
|
|
Expect(metaBackendPath).NotTo(BeADirectory()) |
|
|
|
|
|
|
|
|
Expect(concreteBackendPath).NotTo(BeADirectory()) |
|
|
}) |
|
|
|
|
|
It("should handle meta backend deletion correctly with aliases pointing to the same backend", func() { |
|
|
if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" { |
|
|
Skip("Skipping test on darwin/arm64") |
|
|
} |
|
|
metaBackend := &GalleryBackend{ |
|
|
Metadata: Metadata{ |
|
|
Name: "meta-backend", |
|
|
}, |
|
|
Alias: "meta-backend", |
|
|
CapabilitiesMap: map[string]string{ |
|
|
"nvidia": "nvidia-backend", |
|
|
"amd": "amd-backend", |
|
|
"intel": "intel-backend", |
|
|
}, |
|
|
} |
|
|
|
|
|
nvidiaBackend := &GalleryBackend{ |
|
|
Metadata: Metadata{ |
|
|
Name: "nvidia-backend", |
|
|
}, |
|
|
Alias: "meta-backend", |
|
|
URI: testImage, |
|
|
} |
|
|
|
|
|
amdBackend := &GalleryBackend{ |
|
|
Metadata: Metadata{ |
|
|
Name: "amd-backend", |
|
|
}, |
|
|
Alias: "meta-backend", |
|
|
URI: testImage, |
|
|
} |
|
|
|
|
|
gallery := config.Gallery{ |
|
|
Name: "test-gallery", |
|
|
URL: "file://" + filepath.Join(tempDir, "backend-gallery.yaml"), |
|
|
} |
|
|
|
|
|
galleryBackend := GalleryBackends{amdBackend, nvidiaBackend, metaBackend} |
|
|
|
|
|
dat, err := yaml.Marshal(galleryBackend) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
err = os.WriteFile(filepath.Join(tempDir, "backend-gallery.yaml"), dat, 0644) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
|
|
|
|
|
|
nvidiaSystemState := &system.SystemState{ |
|
|
GPUVendor: "nvidia", |
|
|
VRAM: 1000000000000, |
|
|
Backend: system.Backend{BackendsPath: tempDir}, |
|
|
} |
|
|
err = InstallBackendFromGallery(context.TODO(), []config.Gallery{gallery}, nvidiaSystemState, ml, "meta-backend", nil, true) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
|
|
|
metaBackendPath := filepath.Join(tempDir, "meta-backend") |
|
|
Expect(metaBackendPath).To(BeADirectory()) |
|
|
|
|
|
concreteBackendPath := filepath.Join(tempDir, "nvidia-backend") |
|
|
Expect(concreteBackendPath).To(BeADirectory()) |
|
|
|
|
|
systemState, err := system.GetSystemState( |
|
|
system.WithBackendPath(tempDir), |
|
|
) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
|
|
|
allBackends, err := ListSystemBackends(systemState) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
Expect(allBackends).To(HaveKey("meta-backend")) |
|
|
Expect(allBackends).To(HaveKey("nvidia-backend")) |
|
|
mback, exists := allBackends.Get("meta-backend") |
|
|
Expect(exists).To(BeTrue()) |
|
|
Expect(mback.RunFile).To(Equal(filepath.Join(tempDir, "nvidia-backend", "run.sh"))) |
|
|
|
|
|
|
|
|
err = DeleteBackendFromSystem(systemState, "meta-backend") |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
|
|
|
|
|
|
Expect(metaBackendPath).NotTo(BeADirectory()) |
|
|
|
|
|
|
|
|
Expect(concreteBackendPath).NotTo(BeADirectory()) |
|
|
}) |
|
|
|
|
|
It("should list meta backends correctly in system backends", func() { |
|
|
|
|
|
metaBackendPath := filepath.Join(tempDir, "meta-backend") |
|
|
err := os.MkdirAll(metaBackendPath, 0750) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
|
|
|
|
|
|
metadata := &BackendMetadata{ |
|
|
MetaBackendFor: "concrete-backend", |
|
|
Name: "meta-backend", |
|
|
InstalledAt: "2023-01-01T00:00:00Z", |
|
|
} |
|
|
metadataData, err := json.Marshal(metadata) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
err = os.WriteFile(filepath.Join(metaBackendPath, "metadata.json"), metadataData, 0644) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
|
|
|
|
|
|
concreteBackendPath := filepath.Join(tempDir, "concrete-backend") |
|
|
err = os.MkdirAll(concreteBackendPath, 0750) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
err = os.WriteFile(filepath.Join(concreteBackendPath, "metadata.json"), []byte("{}"), 0755) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
err = os.WriteFile(filepath.Join(concreteBackendPath, "run.sh"), []byte(""), 0755) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
|
|
|
|
|
|
systemState, err := system.GetSystemState( |
|
|
system.WithBackendPath(tempDir), |
|
|
) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
|
|
|
backends, err := ListSystemBackends(systemState) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
|
|
|
metaBackend, exists := backends.Get("meta-backend") |
|
|
concreteBackendRunFile := filepath.Join(tempDir, "concrete-backend", "run.sh") |
|
|
|
|
|
|
|
|
Expect(exists).To(BeTrue()) |
|
|
Expect(backends.Exists("concrete-backend")).To(BeTrue()) |
|
|
|
|
|
|
|
|
Expect(metaBackend.IsMeta).To(BeTrue()) |
|
|
Expect(metaBackend.RunFile).To(Equal(concreteBackendRunFile)) |
|
|
|
|
|
concreteBackend, exists := backends.Get("concrete-backend") |
|
|
Expect(exists).To(BeTrue()) |
|
|
Expect(concreteBackend.RunFile).To(Equal(concreteBackendRunFile)) |
|
|
}) |
|
|
}) |
|
|
|
|
|
Describe("InstallBackend", func() { |
|
|
It("should create base path if it doesn't exist", func() { |
|
|
newPath := filepath.Join(tempDir, "new-path") |
|
|
backend := GalleryBackend{ |
|
|
Metadata: Metadata{ |
|
|
Name: "test-backend", |
|
|
}, |
|
|
URI: "test-uri", |
|
|
} |
|
|
|
|
|
systemState, err := system.GetSystemState( |
|
|
system.WithBackendPath(newPath), |
|
|
) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
err = InstallBackend(context.TODO(), systemState, ml, &backend, nil) |
|
|
Expect(newPath).To(BeADirectory()) |
|
|
Expect(err).To(HaveOccurred()) |
|
|
}) |
|
|
|
|
|
It("should overwrite existing backend", func() { |
|
|
if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" { |
|
|
Skip("Skipping test on darwin/arm64") |
|
|
} |
|
|
newPath := filepath.Join(tempDir, "test-backend") |
|
|
|
|
|
|
|
|
err := os.MkdirAll(newPath, 0750) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
|
|
|
err = os.WriteFile(filepath.Join(newPath, "metadata.json"), []byte("foo"), 0644) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
err = os.WriteFile(filepath.Join(newPath, "run.sh"), []byte(""), 0644) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
|
|
|
backend := GalleryBackend{ |
|
|
Metadata: Metadata{ |
|
|
Name: "test-backend", |
|
|
}, |
|
|
URI: "quay.io/mudler/tests:localai-backend-test", |
|
|
Alias: "test-alias", |
|
|
} |
|
|
|
|
|
systemState, err := system.GetSystemState( |
|
|
system.WithBackendPath(tempDir), |
|
|
) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
err = InstallBackend(context.TODO(), systemState, ml, &backend, nil) |
|
|
Expect(err).ToNot(HaveOccurred()) |
|
|
Expect(filepath.Join(tempDir, "test-backend", "metadata.json")).To(BeARegularFile()) |
|
|
dat, err := os.ReadFile(filepath.Join(tempDir, "test-backend", "metadata.json")) |
|
|
Expect(err).ToNot(HaveOccurred()) |
|
|
Expect(string(dat)).ToNot(Equal("foo")) |
|
|
}) |
|
|
|
|
|
It("should overwrite existing backend", func() { |
|
|
if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" { |
|
|
Skip("Skipping test on darwin/arm64") |
|
|
} |
|
|
newPath := filepath.Join(tempDir, "test-backend") |
|
|
|
|
|
|
|
|
err := os.MkdirAll(newPath, 0750) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
|
|
|
backend := GalleryBackend{ |
|
|
Metadata: Metadata{ |
|
|
Name: "test-backend", |
|
|
}, |
|
|
URI: "quay.io/mudler/tests:localai-backend-test", |
|
|
Alias: "test-alias", |
|
|
} |
|
|
|
|
|
systemState, err := system.GetSystemState( |
|
|
system.WithBackendPath(tempDir), |
|
|
) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
|
|
|
Expect(filepath.Join(tempDir, "test-backend", "metadata.json")).ToNot(BeARegularFile()) |
|
|
|
|
|
err = InstallBackend(context.TODO(), systemState, ml, &backend, nil) |
|
|
Expect(err).ToNot(HaveOccurred()) |
|
|
Expect(filepath.Join(tempDir, "test-backend", "metadata.json")).To(BeARegularFile()) |
|
|
}) |
|
|
|
|
|
It("should create alias file when specified", func() { |
|
|
if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" { |
|
|
Skip("Skipping test on darwin/arm64") |
|
|
} |
|
|
backend := GalleryBackend{ |
|
|
Metadata: Metadata{ |
|
|
Name: "test-backend", |
|
|
}, |
|
|
URI: "quay.io/mudler/tests:localai-backend-test", |
|
|
Alias: "test-alias", |
|
|
} |
|
|
|
|
|
systemState, err := system.GetSystemState( |
|
|
system.WithBackendPath(tempDir), |
|
|
) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
err = InstallBackend(context.TODO(), systemState, ml, &backend, nil) |
|
|
Expect(err).ToNot(HaveOccurred()) |
|
|
Expect(filepath.Join(tempDir, "test-backend", "metadata.json")).To(BeARegularFile()) |
|
|
|
|
|
|
|
|
metadataData, err := os.ReadFile(filepath.Join(tempDir, "test-backend", "metadata.json")) |
|
|
Expect(err).ToNot(HaveOccurred()) |
|
|
var metadata BackendMetadata |
|
|
err = json.Unmarshal(metadataData, &metadata) |
|
|
Expect(err).ToNot(HaveOccurred()) |
|
|
Expect(metadata.Alias).To(Equal("test-alias")) |
|
|
Expect(metadata.Name).To(Equal("test-backend")) |
|
|
|
|
|
Expect(filepath.Join(tempDir, "test-backend", "run.sh")).To(BeARegularFile()) |
|
|
|
|
|
|
|
|
backends, err := ListSystemBackends(systemState) |
|
|
Expect(err).ToNot(HaveOccurred()) |
|
|
aliasBackend, exists := backends.Get("test-alias") |
|
|
Expect(exists).To(BeTrue()) |
|
|
Expect(aliasBackend.RunFile).To(Equal(filepath.Join(tempDir, "test-backend", "run.sh"))) |
|
|
testB, exists := backends.Get("test-backend") |
|
|
Expect(exists).To(BeTrue()) |
|
|
Expect(testB.RunFile).To(Equal(filepath.Join(tempDir, "test-backend", "run.sh"))) |
|
|
}) |
|
|
}) |
|
|
|
|
|
Describe("DeleteBackendFromSystem", func() { |
|
|
It("should delete backend directory", func() { |
|
|
backendName := "test-backend" |
|
|
backendPath := filepath.Join(tempDir, backendName) |
|
|
|
|
|
|
|
|
err := os.MkdirAll(backendPath, 0750) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
|
|
|
err = os.WriteFile(filepath.Join(backendPath, "metadata.json"), []byte("{}"), 0644) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
err = os.WriteFile(filepath.Join(backendPath, "run.sh"), []byte(""), 0644) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
|
|
|
systemState, err := system.GetSystemState( |
|
|
system.WithBackendPath(tempDir), |
|
|
) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
err = DeleteBackendFromSystem(systemState, backendName) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
Expect(backendPath).NotTo(BeADirectory()) |
|
|
}) |
|
|
|
|
|
It("should not error when backend doesn't exist", func() { |
|
|
systemState, err := system.GetSystemState( |
|
|
system.WithBackendPath(tempDir), |
|
|
) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
err = DeleteBackendFromSystem(systemState, "non-existent") |
|
|
Expect(err).To(HaveOccurred()) |
|
|
}) |
|
|
}) |
|
|
|
|
|
Describe("ListSystemBackends", func() { |
|
|
It("should list backends without aliases", func() { |
|
|
|
|
|
backendNames := []string{"backend1", "backend2", "backend3"} |
|
|
for _, name := range backendNames { |
|
|
err := os.MkdirAll(filepath.Join(tempDir, name), 0750) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
err = os.WriteFile(filepath.Join(tempDir, name, "metadata.json"), []byte("{}"), 0755) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
err = os.WriteFile(filepath.Join(tempDir, name, "run.sh"), []byte(""), 0755) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
} |
|
|
|
|
|
systemState, err := system.GetSystemState( |
|
|
system.WithBackendPath(tempDir), |
|
|
) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
backends, err := ListSystemBackends(systemState) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
Expect(backends).To(HaveLen(len(backendNames))) |
|
|
|
|
|
for _, name := range backendNames { |
|
|
backend, exists := backends.Get(name) |
|
|
Expect(exists).To(BeTrue()) |
|
|
Expect(backend.RunFile).To(Equal(filepath.Join(tempDir, name, "run.sh"))) |
|
|
} |
|
|
}) |
|
|
|
|
|
It("should handle backends with aliases", func() { |
|
|
backendName := "backend1" |
|
|
alias := "alias1" |
|
|
backendPath := filepath.Join(tempDir, backendName) |
|
|
|
|
|
|
|
|
err := os.MkdirAll(backendPath, 0750) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
|
|
|
|
|
|
metadata := &BackendMetadata{ |
|
|
Alias: alias, |
|
|
Name: backendName, |
|
|
InstalledAt: "2023-01-01T00:00:00Z", |
|
|
} |
|
|
metadataData, err := json.Marshal(metadata) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
err = os.WriteFile(filepath.Join(backendPath, "metadata.json"), metadataData, 0644) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
err = os.WriteFile(filepath.Join(backendPath, "run.sh"), []byte(""), 0755) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
|
|
|
systemState, err := system.GetSystemState( |
|
|
system.WithBackendPath(tempDir), |
|
|
) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
backends, err := ListSystemBackends(systemState) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
backend, exists := backends.Get(alias) |
|
|
Expect(exists).To(BeTrue()) |
|
|
Expect(backend.RunFile).To(Equal(filepath.Join(tempDir, backendName, "run.sh"))) |
|
|
}) |
|
|
|
|
|
It("should return error when base path doesn't exist", func() { |
|
|
systemState, err := system.GetSystemState( |
|
|
system.WithBackendPath("foobardir"), |
|
|
) |
|
|
Expect(err).NotTo(HaveOccurred()) |
|
|
_, err = ListSystemBackends(systemState) |
|
|
Expect(err).To(HaveOccurred()) |
|
|
}) |
|
|
}) |
|
|
}) |
|
|
|