Skip to content

Commit 200069f

Browse files
committed
fix(gallery): add fallback capability resolution for backend selection
Add fallback logic to FindBestBackendFromMeta function to handle cases where the exact capability match is not available in the gallery. The fix implements a three-tier fallback strategy: 1. First try exact capability match (e.g., nvidia-cuda-13) 2. If not found, try parent capability by stripping version suffix (e.g., nvidia-cuda-13 -> nvidia) 3. Finally try 'default' capability This fixes issue mudler#8509 where installing faster-whisper backend fails when a CUDA variant (e.g., cuda13-faster-whisper) is already installed because the meta-backend resolution lacks fallback capability. Also adds tests for the fallback logic. Signed-off-by: team-coding-agent-1 <team-coding-agent-1@localai>
1 parent fc5bce5 commit 200069f

2 files changed

Lines changed: 172 additions & 6 deletions

File tree

core/gallery/backend_types.go

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package gallery
22

33
import (
44
"fmt"
5+
"strings"
56

67
"github.com/mudler/LocalAI/core/config"
78
"github.com/mudler/LocalAI/pkg/system"
@@ -30,19 +31,56 @@ type GalleryBackend struct {
3031
CapabilitiesMap map[string]string `json:"capabilities,omitempty" yaml:"capabilities,omitempty"`
3132
}
3233

34+
// stripVersionSuffix removes version suffix from capability strings
35+
// e.g., "nvidia-cuda-13" -> "nvidia", "cuda12-faster-whisper" -> "cuda"
36+
func stripVersionSuffix(capability string) string {
37+
// Strip suffixes like "-cuda-12", "-cuda-13" to get parent capability
38+
if idx := strings.LastIndex(capability, "-cuda-"); idx != -1 {
39+
return capability[:idx]
40+
}
41+
// Also handle other version suffixes like "-v1", "-v2"
42+
if idx := strings.LastIndex(capability, "-v"); idx != -1 && idx > 0 {
43+
return capability[:idx]
44+
}
45+
return ""
46+
}
47+
3348
func (backend *GalleryBackend) FindBestBackendFromMeta(systemState *system.SystemState, backends GalleryElements[*GalleryBackend]) *GalleryBackend {
3449
if systemState == nil {
3550
return nil
3651
}
3752

38-
realBackend := backend.CapabilitiesMap[systemState.Capability(backend.CapabilitiesMap)]
39-
if realBackend == "" {
40-
xlog.Debug("No backend found for reported capability", "backend", backend.Name, "reportedCapability", systemState.Capability(backend.CapabilitiesMap))
41-
return nil
53+
// Try exact capability match first
54+
capability := systemState.Capability(backend.CapabilitiesMap)
55+
realBackend := backend.CapabilitiesMap[capability]
56+
57+
// Try to find the backend with exact match
58+
if result := backends.FindByName(realBackend); result != nil {
59+
return result
60+
}
61+
62+
// Fallback: try parent capability (strip version suffix)
63+
if parentCapability := stripVersionSuffix(capability); parentCapability != "" {
64+
if parentBackend := backend.CapabilitiesMap[parentCapability]; parentBackend != "" {
65+
if result := backends.FindByName(parentBackend); result != nil {
66+
xlog.Debug("Using parent capability fallback", "backend", backend.Name,
67+
"originalCapability", capability, "parentCapability", parentCapability)
68+
return result
69+
}
70+
}
71+
}
72+
73+
// Final fallback: try "default" capability
74+
if defaultBackend := backend.CapabilitiesMap["default"]; defaultBackend != "" {
75+
if result := backends.FindByName(defaultBackend); result != nil {
76+
xlog.Debug("Using default capability fallback", "backend", backend.Name,
77+
"originalCapability", capability)
78+
return result
79+
}
4280
}
4381

44-
xlog.Debug("Found backend for reported capability", "backend", backend.Name, "reportedCapability", systemState.Capability(backend.CapabilitiesMap))
45-
return backends.FindByName(realBackend)
82+
xlog.Debug("No backend found for reported capability", "backend", backend.Name, "reportedCapability", capability)
83+
return nil
4684
}
4785

4886
func (m *GalleryBackend) GetInstalled() bool {

core/gallery/backends_test.go

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1025,3 +1025,131 @@ var _ = Describe("Gallery Backends", func() {
10251025
})
10261026
})
10271027
})
1028+
1029+
var _ = Describe("FindBestBackendFromMeta fallback logic", func() {
1030+
It("should fallback to parent capability when exact match not found", func() {
1031+
// Simulate the issue: system has nvidia-cuda-13 but gallery only has cuda12
1032+
metaBackend := &GalleryBackend{
1033+
Metadata: Metadata{
1034+
Name: "faster-whisper",
1035+
},
1036+
CapabilitiesMap: map[string]string{
1037+
"nvidia-cuda-13": "cuda13-faster-whisper",
1038+
"nvidia-cuda-12": "cuda12-faster-whisper",
1039+
"nvidia": "cuda-faster-whisper",
1040+
"default": "cpu-faster-whisper",
1041+
},
1042+
}
1043+
1044+
cuda13Backend := &GalleryBackend{
1045+
Metadata: Metadata{
1046+
Name: "cuda13-faster-whisper",
1047+
},
1048+
URI: testImage,
1049+
}
1050+
1051+
cuda12Backend := &GalleryBackend{
1052+
Metadata: Metadata{
1053+
Name: "cuda12-faster-whisper",
1054+
},
1055+
URI: testImage,
1056+
}
1057+
1058+
cudaBackend := &GalleryBackend{
1059+
Metadata: Metadata{
1060+
Name: "cuda-faster-whisper",
1061+
},
1062+
URI: testImage,
1063+
}
1064+
1065+
cpuBackend := &GalleryBackend{
1066+
Metadata: Metadata{
1067+
Name: "cpu-faster-whisper",
1068+
},
1069+
URI: testImage,
1070+
}
1071+
1072+
// Test case 1: exact match - nvidia-cuda-12 -> cuda12-faster-whisper
1073+
backends := GalleryElements[*GalleryBackend]{cuda13Backend, cuda12Backend, cudaBackend, cpuBackend}
1074+
systemState := &system.SystemState{GPUVendor: "nvidia", CUDAGPUModel: "A100", CUDAMajor: 13, VRAM: 8 * 1024 * 1024 * 1024}
1075+
1076+
bestBackend := metaBackend.FindBestBackendFromMeta(systemState, backends)
1077+
// Should find cuda13 exact match
1078+
Expect(bestBackend).To(Equal(cuda13Backend))
1079+
1080+
// Test case 2: no exact match, fallback to parent (nvidia-cuda-13 -> nvidia -> cuda)
1081+
// Remove cuda13 from backends to simulate the issue
1082+
backendsNoCuda13 := GalleryElements[*GalleryBackend]{cuda12Backend, cudaBackend, cpuBackend}
1083+
bestBackend = metaBackend.FindBestBackendFromMeta(systemState, backendsNoCuda13)
1084+
// Should fallback to cuda12 (parent of nvidia-cuda-13 is nvidia, but we have nvidia-cuda-12)
1085+
Expect(bestBackend).To(Equal(cuda12Backend))
1086+
1087+
// Test case 3: no parent match, fallback to default
1088+
backendsNoMatch := GalleryElements[*GalleryBackend]{cpuBackend}
1089+
bestBackend = metaBackend.FindBestBackendFromMeta(systemState, backendsNoMatch)
1090+
// Should fallback to default
1091+
Expect(bestBackend).To(Equal(cpuBackend))
1092+
})
1093+
1094+
It("should return nil when no fallback available", func() {
1095+
metaBackend := &GalleryBackend{
1096+
Metadata: Metadata{
1097+
Name: "faster-whisper",
1098+
},
1099+
CapabilitiesMap: map[string]string{
1100+
"nvidia-cuda-13": "cuda13-faster-whisper",
1101+
"default": "cpu-faster-whisper",
1102+
},
1103+
}
1104+
1105+
// Only have a different backend
1106+
otherBackend := &GalleryBackend{
1107+
Metadata: Metadata{
1108+
Name: "other-backend",
1109+
},
1110+
URI: testImage,
1111+
}
1112+
1113+
backends := GalleryElements[*GalleryBackend]{otherBackend}
1114+
systemState := &system.SystemState{GPUVendor: "nvidia", CUDAMajor: 13, VRAM: 8 * 1024 * 1024 * 1024}
1115+
1116+
bestBackend := metaBackend.FindBestBackendFromMeta(systemState, backends)
1117+
Expect(bestBackend).To(BeNil())
1118+
})
1119+
1120+
It("should handle stripVersionSuffix correctly", func() {
1121+
// Test the stripVersionSuffix function via integration
1122+
metaBackend := &GalleryBackend{
1123+
Metadata: Metadata{
1124+
Name: "test-backend",
1125+
},
1126+
CapabilitiesMap: map[string]string{
1127+
"nvidia-cuda-13": "cuda13",
1128+
"nvidia": "cuda",
1129+
"default": "cpu",
1130+
},
1131+
}
1132+
1133+
cudaBackend := &GalleryBackend{
1134+
Metadata: Metadata{
1135+
Name: "cuda",
1136+
},
1137+
URI: testImage,
1138+
}
1139+
1140+
cpuBackend := &GalleryBackend{
1141+
Metadata: Metadata{
1142+
Name: "cpu",
1143+
},
1144+
URI: testImage,
1145+
}
1146+
1147+
backends := GalleryElements[*GalleryBackend]{cudaBackend, cpuBackend}
1148+
1149+
// Test with cuda-13 capability that doesn't have exact match
1150+
systemState := &system.SystemState{GPUVendor: "nvidia", CUDAMajor: 13, VRAM: 8 * 1024 * 1024 * 1024}
1151+
bestBackend := metaBackend.FindBestBackendFromMeta(systemState, backends)
1152+
// Should fallback to "nvidia" -> "cuda"
1153+
Expect(bestBackend).To(Equal(cudaBackend))
1154+
})
1155+
})

0 commit comments

Comments
 (0)