diff --git a/src/components/screens/Launch/GeneralTab/BackendSelector.tsx b/src/components/screens/Launch/GeneralTab/BackendSelector.tsx index 2334500..8172f68 100644 --- a/src/components/screens/Launch/GeneralTab/BackendSelector.tsx +++ b/src/components/screens/Launch/GeneralTab/BackendSelector.tsx @@ -40,34 +40,19 @@ export const BackendSelector = ({ useEffect(() => { const loadBackends = async () => { try { - const [currentBinaryInfo, cpuCapabilitiesResult, gpuCapabilities] = - await Promise.all([ - window.electronAPI.kobold.getCurrentBinaryInfo(), - window.electronAPI.kobold.detectCPU(), - window.electronAPI.kobold.detectGPUCapabilities(), - ]); + const [cpuCapabilitiesResult, backends] = await Promise.all([ + window.electronAPI.kobold.detectCPU(), + window.electronAPI.kobold.getAvailableBackends(), + ]); setCpuCapabilities({ avx: cpuCapabilitiesResult.avx, avx2: cpuCapabilitiesResult.avx2, }); - let backends: Array<{ - value: string; - label: string; - devices?: string[]; - }> = []; - - if (currentBinaryInfo?.path) { - backends = await window.electronAPI.kobold.getAvailableBackends( - currentBinaryInfo.path, - gpuCapabilities - ); - - const cpuBackend = backends.find((b) => b.value === 'cpu'); - if (cpuBackend) { - cpuBackend.devices = cpuCapabilitiesResult.devices; - } + const cpuBackend = backends.find((b) => b.value === 'cpu'); + if (cpuBackend) { + cpuBackend.devices = cpuCapabilitiesResult.devices; } setAvailableBackends(backends); @@ -231,7 +216,7 @@ export const BackendSelector = ({ step={1} size="sm" w={80} - disabled={autoGpuLayers} + disabled={autoGpuLayers || backend === 'cpu'} /> diff --git a/src/components/screens/Launch/index.tsx b/src/components/screens/Launch/index.tsx index 28c3043..bcae641 100644 --- a/src/components/screens/Launch/index.tsx +++ b/src/components/screens/Launch/index.tsx @@ -76,10 +76,7 @@ export const LaunchScreen = ({ const setHappyDefaults = useCallback(async () => { try { - const backends = await window.electronAPI.kobold.getAvailableBackends( - (await window.electronAPI.kobold.getCurrentBinaryInfo())?.path || '', - await window.electronAPI.kobold.detectGPUCapabilities() - ); + const backends = await window.electronAPI.kobold.getAvailableBackends(); if (!backend && backends.length > 0) { handleBackendChange(backends[0].value); diff --git a/src/main/index.ts b/src/main/index.ts index ac3e918..e5350d7 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -35,7 +35,6 @@ class FriendlyKoboldApp { this.windowManager = new WindowManager(); this.githubService = new GitHubService(this.logManager); this.hardwareService = new HardwareService(); - this.binaryService = new BinaryService(this.logManager); this.koboldManager = new KoboldCppManager( this.configManager, @@ -44,6 +43,12 @@ class FriendlyKoboldApp { this.logManager ); + this.binaryService = new BinaryService( + this.logManager, + this.koboldManager, + this.hardwareService + ); + this.ipcHandlers = new IPCHandlers( this.koboldManager, this.configManager, diff --git a/src/main/services/BinaryService.ts b/src/main/services/BinaryService.ts index cd27af6..10aba70 100644 --- a/src/main/services/BinaryService.ts +++ b/src/main/services/BinaryService.ts @@ -1,6 +1,8 @@ import { existsSync } from 'fs'; import { join, dirname } from 'path'; import { LogManager } from '@/main/managers/LogManager'; +import type { KoboldCppManager } from '@/main/managers/KoboldCppManager'; +import type { HardwareService } from '@/main/services/HardwareService'; export interface BackendSupport { rocm: boolean; @@ -18,9 +20,17 @@ export class BinaryService { Array<{ value: string; label: string; devices?: string[] }> >(); private logManager: LogManager; + private koboldManager: KoboldCppManager; + private hardwareService: HardwareService; - constructor(logManager: LogManager) { + constructor( + logManager: LogManager, + koboldManager: KoboldCppManager, + hardwareService: HardwareService + ) { this.logManager = logManager; + this.koboldManager = koboldManager; + this.hardwareService = hardwareService; } detectBackendSupport(koboldBinaryPath: string): BackendSupport { @@ -46,7 +56,18 @@ export class BinaryService { const hasKoboldCppLib = (name: string) => { const filename = `${name}${libExtension}`; - return existsSync(join(internalDir, filename)); + + if (platform === 'win32') { + return ( + existsSync(join(binaryDir, filename)) || + existsSync(join(internalDir, filename)) + ); + } else { + return ( + existsSync(join(internalDir, filename)) || + existsSync(join(binaryDir, filename)) + ); + } }; support.rocm = hasKoboldCppLib('koboldcpp_hipblas'); @@ -66,67 +87,78 @@ export class BinaryService { return support; } - getAvailableBackends( - koboldBinaryPath: string, - hardwareCapabilities?: { - cuda: { supported: boolean; devices: string[] }; - rocm: { supported: boolean; devices: string[] }; - vulkan: { supported: boolean; devices: string[] }; - clblast: { supported: boolean; devices: string[] }; - } - ): Array<{ value: string; label: string; devices?: string[] }> { - const cacheKey = `${koboldBinaryPath}:${JSON.stringify(hardwareCapabilities)}`; + async getAvailableBackends(): Promise< + Array<{ value: string; label: string; devices?: string[] }> + > { + try { + const [currentBinaryInfo, hardwareCapabilities] = await Promise.all([ + this.koboldManager.getCurrentBinaryInfo(), + this.hardwareService.detectGPUCapabilities(), + ]); - if (this.availableBackendsCache.has(cacheKey)) { - return this.availableBackendsCache.get(cacheKey)!; - } + if (!currentBinaryInfo?.path) { + return [{ value: 'cpu', label: 'CPU' }]; + } - const backendSupport = this.detectBackendSupport(koboldBinaryPath); - const backends: Array<{ - value: string; - label: string; - devices?: string[]; - }> = []; + const cacheKey = `${currentBinaryInfo.path}:${JSON.stringify(hardwareCapabilities)}`; + + if (this.availableBackendsCache.has(cacheKey)) { + return this.availableBackendsCache.get(cacheKey)!; + } + + const backendSupport = this.detectBackendSupport(currentBinaryInfo.path); + const backends: Array<{ + value: string; + label: string; + devices?: string[]; + }> = []; + + if (backendSupport.cuda && hardwareCapabilities.cuda.supported) { + backends.push({ + value: 'cuda', + label: 'CUDA', + devices: hardwareCapabilities.cuda.devices, + }); + } + + if (backendSupport.rocm && hardwareCapabilities.rocm.supported) { + backends.push({ + value: 'rocm', + label: 'ROCm', + devices: hardwareCapabilities.rocm.devices, + }); + } + + if (backendSupport.vulkan && hardwareCapabilities.vulkan.supported) { + backends.push({ + value: 'vulkan', + label: 'Vulkan', + devices: hardwareCapabilities.vulkan.devices, + }); + } + + if (backendSupport.clblast && hardwareCapabilities.clblast.supported) { + backends.push({ + value: 'clblast', + label: 'CLBlast', + devices: hardwareCapabilities.clblast.devices, + }); + } - if (backendSupport.cuda && hardwareCapabilities?.cuda.supported) { backends.push({ - value: 'cuda', - label: 'CUDA', - devices: hardwareCapabilities.cuda.devices, + value: 'cpu', + label: 'CPU', }); + + this.availableBackendsCache.set(cacheKey, backends); + return backends; + } catch (error) { + this.logManager.logError( + 'Failed to get available backends:', + error as Error + ); + return [{ value: 'cpu', label: 'CPU' }]; } - - if (backendSupport.rocm && hardwareCapabilities?.rocm.supported) { - backends.push({ - value: 'rocm', - label: 'ROCm', - devices: hardwareCapabilities.rocm.devices, - }); - } - - if (backendSupport.vulkan && hardwareCapabilities?.vulkan.supported) { - backends.push({ - value: 'vulkan', - label: 'Vulkan', - devices: hardwareCapabilities.vulkan.devices, - }); - } - - if (backendSupport.clblast && hardwareCapabilities?.clblast.supported) { - backends.push({ - value: 'clblast', - label: 'CLBlast', - devices: hardwareCapabilities.clblast.devices, - }); - } - - backends.push({ - value: 'cpu', - label: 'CPU', - }); - - this.availableBackendsCache.set(cacheKey, backends); - return backends; } clearCache(): void { diff --git a/src/main/services/HardwareService.ts b/src/main/services/HardwareService.ts index 6bf0ee8..4ce034b 100644 --- a/src/main/services/HardwareService.ts +++ b/src/main/services/HardwareService.ts @@ -125,6 +125,7 @@ export class HardwareService { ]); this.gpuCapabilitiesCache = { cuda, rocm, vulkan, clblast }; + return this.gpuCapabilitiesCache; } @@ -303,10 +304,14 @@ export class HardwareService { const lines = output.split('\n'); for (const line of lines) { - if (line.includes('deviceName')) { - const name = line.split('=')[1]?.trim(); - if (name) { - devices.push(shortenDeviceName(name)); + // Handle both formats: "deviceName = AMD Radeon RX 7900 GRE" and other potential formats + if (line.includes('deviceName') && line.includes('=')) { + const parts = line.split('='); + if (parts.length >= 2) { + const name = parts[1]?.trim(); + if (name) { + devices.push(shortenDeviceName(name)); + } } } } @@ -343,29 +348,23 @@ export class HardwareService { const lines = output.split('\n'); let currentPlatform = ''; - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - // Extract platform name + for (let i = 0; i < lines.length; i++) { + const line = lines[i].trim(); + + // Extract platform name - this appears early in the output if (line.includes('Platform Name:')) { currentPlatform = line.split('Platform Name:')[1]?.trim() || ''; continue; } - // Extract device names for GPU devices - if (line.includes('Device Name:')) { - const deviceName = line.split('Device Name:')[1]?.trim(); - if (deviceName) { - // Look ahead to check if this is a GPU device - for (let j = i + 1; j < Math.min(i + 10, lines.length); j++) { - if (lines[j].includes('Device Type:') && lines[j].includes('GPU')) { - const deviceLabel = currentPlatform - ? `${shortenDeviceName(deviceName)} (${currentPlatform})` - : shortenDeviceName(deviceName); - devices.push(deviceLabel); - break; - } - } + // When we find a GPU device type, look for device name + if (line.includes('Device Type:') && line.includes('GPU')) { + const deviceName = this.findDeviceNameInClInfo(lines, i); + + if (deviceName && currentPlatform) { + const deviceLabel = `${shortenDeviceName(deviceName)} (${currentPlatform})`; + devices.push(deviceLabel); } } } @@ -373,6 +372,34 @@ export class HardwareService { return devices; } + private findDeviceNameInClInfo(lines: string[], startIndex: number): string { + // Look for Board name first (appears closer to Device Type and is more descriptive) + for ( + let j = startIndex + 1; + j < Math.min(startIndex + 50, lines.length); + j++ + ) { + const nextLine = lines[j].trim(); + if (nextLine.includes('Board name:')) { + return nextLine.split('Board name:')[1]?.trim() || ''; + } + } + + // If no Board name found, look for Name: field (appears much later) + for ( + let j = startIndex + 1; + j < Math.min(startIndex + 100, lines.length); + j++ + ) { + const nextLine = lines[j].trim(); + if (nextLine.startsWith('Name:')) { + return nextLine.split('Name:')[1]?.trim() || ''; + } + } + + return ''; + } + private async detectCLBlast(): Promise<{ supported: boolean; devices: string[]; diff --git a/src/main/utils/IPCHandlers.ts b/src/main/utils/IPCHandlers.ts index 7c4c45a..91d6213 100644 --- a/src/main/utils/IPCHandlers.ts +++ b/src/main/utils/IPCHandlers.ts @@ -6,7 +6,6 @@ import { LogManager } from '@/main/managers/LogManager'; import { GitHubService } from '@/main/services/GitHubService'; import { HardwareService } from '@/main/services/HardwareService'; import { BinaryService } from '@/main/services/BinaryService'; -import type { GPUCapabilities } from '@/types/hardware'; export class IPCHandlers { private koboldManager: KoboldCppManager; @@ -125,13 +124,8 @@ export class IPCHandlers { this.binaryService.detectBackendSupport(binaryPath) ); - ipcMain.handle( - 'kobold:getAvailableBackends', - (_, binaryPath: string, hardwareCapabilities: GPUCapabilities) => - this.binaryService.getAvailableBackends( - binaryPath, - hardwareCapabilities - ) + ipcMain.handle('kobold:getAvailableBackends', () => + this.binaryService.getAvailableBackends() ); ipcMain.handle('kobold:getPlatform', () => ({ diff --git a/src/preload/index.ts b/src/preload/index.ts index 2b1452f..9c21655 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -6,7 +6,6 @@ import type { LogsAPI, UpdateInfo, } from '@/types/electron'; -import type { GPUCapabilities } from '@/types/hardware'; const koboldAPI: KoboldAPI = { getInstalledVersion: () => ipcRenderer.invoke('kobold:getInstalledVersion'), @@ -32,15 +31,7 @@ const koboldAPI: KoboldAPI = { ipcRenderer.invoke('kobold:detectAllCapabilities'), detectBackendSupport: (binaryPath: string) => ipcRenderer.invoke('kobold:detectBackendSupport', binaryPath), - getAvailableBackends: ( - binaryPath: string, - hardwareCapabilities: GPUCapabilities - ) => - ipcRenderer.invoke( - 'kobold:getAvailableBackends', - binaryPath, - hardwareCapabilities - ), + getAvailableBackends: () => ipcRenderer.invoke('kobold:getAvailableBackends'), getCurrentInstallDir: () => ipcRenderer.invoke('kobold:getCurrentInstallDir'), selectInstallDirectory: () => ipcRenderer.invoke('kobold:selectInstallDirectory'), diff --git a/src/types/electron.d.ts b/src/types/electron.d.ts index 43fbb8d..192381d 100644 --- a/src/types/electron.d.ts +++ b/src/types/electron.d.ts @@ -81,10 +81,9 @@ export interface KoboldAPI { failsafe: boolean; cuda: boolean; }>; - getAvailableBackends: ( - binaryPath: string, - hardwareCapabilities: GPUCapabilities - ) => Promise>; + getAvailableBackends: () => Promise< + Array<{ value: string; label: string; devices?: string[] }> + >; getCurrentInstallDir: () => Promise; selectInstallDirectory: () => Promise; downloadRelease: (