From 48338b99047dc5cc8aa369e706f830dc794ce9be Mon Sep 17 00:00:00 2001 From: Egor Date: Fri, 26 Sep 2025 13:02:05 -0700 Subject: [PATCH] windows hardware detection improvements, hide integrated gpus for the clblast backend, dont try to detect avx/avx2 flags in the CPU as it's unreliable, use --usecpu flag when CPU backend is selected --- .../Launch/GeneralTab/BackendSelectItem.tsx | 76 ++++++++++------- .../Launch/GeneralTab/GpuDeviceSelector.tsx | 61 ++++++++++---- src/components/screens/Launch/index.tsx | 2 - src/hooks/useLaunchLogic.ts | 15 +++- src/hooks/useWarnings.ts | 50 ++--------- src/main/modules/hardware.ts | 82 +++++++++++-------- src/types/hardware.d.ts | 9 +- src/types/index.d.ts | 5 +- src/utils/node/gpu.ts | 74 ++++++----------- src/utils/node/vulkan.ts | 24 +++++- src/utils/systemInfo.ts | 4 +- 11 files changed, 219 insertions(+), 183 deletions(-) diff --git a/src/components/screens/Launch/GeneralTab/BackendSelectItem.tsx b/src/components/screens/Launch/GeneralTab/BackendSelectItem.tsx index 2c3f8d0..5a41052 100644 --- a/src/components/screens/Launch/GeneralTab/BackendSelectItem.tsx +++ b/src/components/screens/Launch/GeneralTab/BackendSelectItem.tsx @@ -7,31 +7,51 @@ export const BackendSelectItem = ({ label, devices, disabled = false, -}: BackendSelectItemProps) => ( - - - - {label} - {disabled && ( - - (Compatible devices not found) - - )} - - - {devices && devices.length > 0 && ( - - {devices.slice(0, 2).map((device, index) => ( - - {device.length > 25 ? `${device.slice(0, 25)}...` : device} - - ))} - {devices.length > 2 && ( - - +{devices.length - 2} - - )} - - )} - -); +}: BackendSelectItemProps) => { + const renderDeviceName = ( + device: string | { name: string; isIntegrated: boolean } + ) => { + const deviceName = typeof device === 'string' ? device : device.name; + return deviceName.length > 25 + ? `${deviceName.slice(0, 25)}...` + : deviceName; + }; + + return ( + + + + {label} + {disabled && ( + + (Compatible devices not found) + + )} + + + {devices && + devices.length > 0 && + (() => { + const discreteDevices = devices.filter( + (device) => typeof device === 'string' || !device.isIntegrated + ); + return ( + discreteDevices.length > 0 && ( + + {discreteDevices.slice(0, 2).map((device, index) => ( + + {renderDeviceName(device)} + + ))} + {discreteDevices.length > 2 && ( + + +{discreteDevices.length - 2} + + )} + + ) + ); + })()} + + ); +}; diff --git a/src/components/screens/Launch/GeneralTab/GpuDeviceSelector.tsx b/src/components/screens/Launch/GeneralTab/GpuDeviceSelector.tsx index 250a320..b478e2b 100644 --- a/src/components/screens/Launch/GeneralTab/GpuDeviceSelector.tsx +++ b/src/components/screens/Launch/GeneralTab/GpuDeviceSelector.tsx @@ -25,8 +25,18 @@ export const GpuDeviceSelector = ({ backend === 'rocm' || backend === 'vulkan' || backend === 'clblast'; - const hasMultipleDevices = - selectedBackend?.devices && selectedBackend.devices.length > 1; + + const getDiscreteDeviceCount = () => { + if (!selectedBackend?.devices) return 0; + if (backend === 'clblast') { + return selectedBackend.devices.filter( + (device) => typeof device === 'string' || !device.isIntegrated + ).length; + } + return selectedBackend.devices.length; + }; + + const hasMultipleDevices = getDiscreteDeviceCount() > 1; const showTensorSplit = (backend === 'cuda' || backend === 'rocm' || backend === 'vulkan') && hasMultipleDevices && @@ -36,19 +46,42 @@ export const GpuDeviceSelector = ({ return null; } - const deviceOptions = - backend === 'clblast' - ? selectedBackend.devices!.map((device, index) => ({ - value: index.toString(), - label: `GPU ${index}: ${device}`, - })) - : [ - { value: 'all', label: 'All GPUs' }, - ...selectedBackend.devices!.map((device, index) => ({ + const deviceOptions = (() => { + if (!selectedBackend?.devices) return []; + + if (backend === 'clblast') { + return selectedBackend.devices + .map((device, index) => { + if (typeof device === 'object' && device.isIntegrated) { + return null; + } + const deviceName = typeof device === 'string' ? device : device.name; + return { value: index.toString(), - label: `GPU ${index}: ${device}`, - })), - ]; + label: `GPU ${index}: ${deviceName}`, + }; + }) + .filter( + (option): option is NonNullable => option !== null + ); + } + + return [ + { value: 'all', label: 'All GPUs' }, + ...selectedBackend.devices.map((device, index) => { + const deviceName = + typeof device === 'string' + ? device + : typeof device === 'object' && 'name' in device + ? device.name + : String(device); + return { + value: index.toString(), + label: `GPU ${index}: ${deviceName}`, + }; + }), + ]; + })(); return (
diff --git a/src/components/screens/Launch/index.tsx b/src/components/screens/Launch/index.tsx index 5b3f96d..ed0b18c 100644 --- a/src/components/screens/Launch/index.tsx +++ b/src/components/screens/Launch/index.tsx @@ -75,8 +75,6 @@ export const LaunchScreen = ({ onLaunch }: LaunchScreenProps) => { model, sdmodel, backend, - noavx2, - failsafe, configLoaded, }); diff --git a/src/hooks/useLaunchLogic.ts b/src/hooks/useLaunchLogic.ts index 188efa5..0ca8bc0 100644 --- a/src/hooks/useLaunchLogic.ts +++ b/src/hooks/useLaunchLogic.ts @@ -85,10 +85,14 @@ const buildModelArgs = ( const buildConfigArgs = (isImageMode: boolean, launchArgs: LaunchArgs) => { const args: string[] = []; - if (launchArgs.autoGpuLayers) { - args.push('--gpulayers', '-1'); - } else if (launchArgs.gpuLayers > 0) { - args.push('--gpulayers', launchArgs.gpuLayers.toString()); + const isGpuBackend = launchArgs.backend && launchArgs.backend !== 'cpu'; + + if (isGpuBackend) { + if (launchArgs.autoGpuLayers) { + args.push('--gpulayers', '-1'); + } else if (launchArgs.gpuLayers > 0) { + args.push('--gpulayers', launchArgs.gpuLayers.toString()); + } } if (launchArgs.contextSize) { @@ -193,6 +197,9 @@ const buildBackendArgs = (launchArgs: LaunchArgs) => { const args: string[] = []; if (!launchArgs.backend || launchArgs.backend === 'cpu') { + if (launchArgs.backend === 'cpu') { + args.push('--usecpu'); + } return args; } diff --git a/src/hooks/useWarnings.ts b/src/hooks/useWarnings.ts index f6945de..bf3fd1d 100644 --- a/src/hooks/useWarnings.ts +++ b/src/hooks/useWarnings.ts @@ -10,8 +10,6 @@ interface UseWarningsProps { model: string; sdmodel: string; backend?: string; - noavx2?: boolean; - failsafe?: boolean; configLoaded?: boolean; } @@ -133,9 +131,6 @@ const checkVramWarnings = async (backend: string): Promise => { const checkCpuWarnings = ( backend: string, - cpuCapabilities: { avx: boolean; avx2: boolean }, - noavx2: boolean, - failsafe: boolean, availableBackends: BackendOption[] ) => { const warnings: Warning[] = []; @@ -144,22 +139,6 @@ const checkCpuWarnings = ( return warnings; } - if (!cpuCapabilities.avx2 && !noavx2) { - warnings.push({ - type: 'warning', - message: - 'Your CPU does not support AVX2. Enable the "Disable AVX2" option on the Advanced tab to avoid crashes.', - }); - } - - if (!cpuCapabilities.avx && !cpuCapabilities.avx2 && !failsafe) { - warnings.push({ - type: 'warning', - message: - 'Your CPU does not support AVX or AVX2. Enable the "Failsafe" option on the Advanced tab to avoid crashes.', - }); - } - if ( availableBackends.length > 0 && availableBackends.some((b) => b.value === 'cpu') @@ -177,11 +156,8 @@ const checkCpuWarnings = ( const checkBackendWarnings = async (params?: { backend: string; cpuCapabilities: { - avx: boolean; - avx2: boolean; + devices: string[]; } | null; - noavx2: boolean; - failsafe: boolean; availableBackends: BackendOption[]; }): Promise => { const warnings: Warning[] = []; @@ -204,20 +180,13 @@ const checkBackendWarnings = async (params?: { warnings.push(...gpuWarnings); if (params) { - const { backend, cpuCapabilities, noavx2, failsafe, availableBackends } = - params; + const { backend, cpuCapabilities, availableBackends } = params; const vramWarnings = await checkVramWarnings(backend); warnings.push(...vramWarnings); if (cpuCapabilities) { - const cpuWarnings = checkCpuWarnings( - backend, - cpuCapabilities, - noavx2, - failsafe, - availableBackends - ); + const cpuWarnings = checkCpuWarnings(backend, availableBackends); warnings.push(...cpuWarnings); } } @@ -229,8 +198,6 @@ export const useWarnings = ({ model, sdmodel, backend, - noavx2 = false, - failsafe = false, configLoaded = false, }: UseWarningsProps) => { const [backendWarnings, setBackendWarnings] = useState([]); @@ -251,21 +218,14 @@ export const useWarnings = ({ window.electronAPI.kobold.getAvailableBackends(), ]); - const cpuCapabilities = { - avx: cpuCapabilitiesResult.avx, - avx2: cpuCapabilitiesResult.avx2, - }; - const result = await checkBackendWarnings({ backend, - cpuCapabilities, - noavx2, - failsafe, + cpuCapabilities: cpuCapabilitiesResult, availableBackends, }); setBackendWarnings(result); - }, [backend, noavx2, failsafe]); + }, [backend]); useEffect(() => { updateBackendWarnings(); diff --git a/src/main/modules/hardware.ts b/src/main/modules/hardware.ts index 66fcf8f..04619ce 100644 --- a/src/main/modules/hardware.ts +++ b/src/main/modules/hardware.ts @@ -1,7 +1,6 @@ /* eslint-disable no-comments/disallowComments */ import { cpu as siCpu, - cpuFlags, mem as siMem, memLayout as siMemLayout, } from 'systeminformation'; @@ -16,7 +15,11 @@ import type { import { execa } from 'execa'; import { formatDeviceName } from '@/utils/format'; import { platform } from 'process'; -import { getVulkanInfo, detectLinuxGPUViaVulkan } from '@/utils/node/vulkan'; +import { + getVulkanInfo, + detectLinuxGPUViaVulkan, + detectVulkan, +} from '@/utils/node/vulkan'; const COMMON_EXEC_OPTIONS = { timeout: 3000, @@ -34,7 +37,7 @@ export async function detectCPU() { } const result = await safeExecute(async () => { - const [cpu, flags] = await Promise.all([siCpu(), cpuFlags()]); + const cpu = await siCpu(); const devices: string[] = []; if (cpu.brand) { @@ -43,12 +46,7 @@ export async function detectCPU() { ); } - const avx = flags.includes('avx') || flags.includes('AVX'); - const avx2 = flags.includes('avx2') || flags.includes('AVX2'); - const capabilities = { - avx, - avx2, devices, }; @@ -57,8 +55,6 @@ export async function detectCPU() { }, 'CPU detection failed'); const fallbackCapabilities = { - avx: false, - avx2: false, devices: [], }; @@ -74,15 +70,15 @@ export async function detectGPU() { const result = await safeExecute(async () => { if (platform === 'linux') { return detectLinuxGPUViaVulkan(); - } else { - return detectGPUViaSI(); } + + return detectGPUViaSI(); }, 'GPU detection failed'); const fallbackGPUInfo = { hasAMD: false, hasNVIDIA: false, - gpuInfo: ['GPU detection failed'], + gpuInfo: [], }; basicGPUInfoCache = result || fallbackGPUInfo; @@ -317,28 +313,8 @@ export async function detectROCm() { } } -async function detectVulkan() { - try { - const vulkanInfo = await getVulkanInfo(); - - const devices: string[] = []; - - for (const gpu of vulkanInfo.discreteGPUs) { - devices.push(formatDeviceName(gpu.deviceName)); - } - - return { - supported: devices.length > 0, - devices, - version: vulkanInfo.apiVersion || 'Unknown', - }; - } catch { - return { supported: false, devices: [], version: 'Unknown' }; - } -} - function parseClInfoOutput(output: string) { - const devices: string[] = []; + const devices: { name: string; isIntegrated: boolean }[] = []; const lines = output.split('\n'); let currentPlatform = ''; @@ -353,9 +329,13 @@ function parseClInfoOutput(output: string) { if (line.includes('Device Type:') && line.includes('GPU')) { const deviceName = findDeviceNameInClInfo(lines, i); + const computeUnits = findComputeUnitsInClInfo(lines, i); if (deviceName && currentPlatform) { - devices.push(formatDeviceName(deviceName)); + devices.push({ + name: formatDeviceName(deviceName), + isIntegrated: !isDiscreteGPU(deviceName, computeUnits), + }); } } } @@ -389,6 +369,38 @@ function findDeviceNameInClInfo(lines: string[], startIndex: number) { return ''; } +function findComputeUnitsInClInfo(lines: string[], startIndex: number) { + for ( + let j = startIndex + 1; + j < Math.min(startIndex + 50, lines.length); + j++ + ) { + const nextLine = lines[j].trim(); + if (nextLine.includes('Max compute units:')) { + const units = nextLine.split('Max compute units:')[1]?.trim(); + return units ? parseInt(units, 10) : 0; + } + } + return 0; +} + +function isDiscreteGPU(deviceName: string, computeUnits: number) { + const lowerName = deviceName.toLowerCase(); + + if ( + lowerName.includes('radeon(tm) graphics') || + lowerName.includes('intel') + ) { + return false; + } + + if (computeUnits <= 2) { + return false; + } + + return true; +} + async function detectCLBlast() { try { const { stdout } = await execa('clinfo', [], COMMON_EXEC_OPTIONS); diff --git a/src/types/hardware.d.ts b/src/types/hardware.d.ts index dd246e1..050ee1a 100644 --- a/src/types/hardware.d.ts +++ b/src/types/hardware.d.ts @@ -1,6 +1,4 @@ export interface CPUCapabilities { - avx: boolean; - avx2: boolean; devices: string[]; } @@ -34,7 +32,7 @@ export interface GPUCapabilities { }; clblast: { readonly supported: boolean; - readonly devices: readonly string[]; + readonly devices: readonly CLBlastDevice[]; readonly version?: string; }; } @@ -45,6 +43,11 @@ export interface BasicGPUInfo { gpuInfo: string[]; } +export interface CLBlastDevice { + readonly name: string; + readonly isIntegrated: boolean; +} + export interface HardwareDetectionResult { readonly supported: boolean; readonly devices: readonly string[]; diff --git a/src/types/index.d.ts b/src/types/index.d.ts index dd8c362..117e74a 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -72,7 +72,10 @@ export interface SelectOption { } export interface BackendOption extends SelectOption { - readonly devices?: readonly string[]; + readonly devices?: readonly ( + | string + | { name: string; isIntegrated: boolean } + )[]; readonly disabled?: boolean; } diff --git a/src/utils/node/gpu.ts b/src/utils/node/gpu.ts index 4ffb486..a695cd2 100644 --- a/src/utils/node/gpu.ts +++ b/src/utils/node/gpu.ts @@ -1,8 +1,8 @@ import { readFile, readdir } from 'fs/promises'; import { join } from 'path'; import { platform } from 'process'; -import { execa } from 'execa'; import { graphics as siGraphics } from 'systeminformation'; +import { formatDeviceName } from '../format'; interface CachedGPUInfo { devicePath: string; @@ -167,50 +167,23 @@ async function getLinuxGPUData() { async function getWindowsGPUData() { try { - const { stdout } = await execa( - 'powershell', - [ - '-Command', - `$activeGpus = Get-CimInstance -ClassName Win32_VideoController | Where-Object { $_.Status -eq 'OK' -and $_.Name -notlike '*Intel*UHD*' -and $_.Name -notlike '*Intel*Iris*' -and $_.Name -notlike '*Basic Display*' } | Select-Object -ExpandProperty Name; - $gpuKeys = Get-ChildItem 'HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Class\\{4d36e968-e325-11ce-bfc1-08002be10318}' | Where-Object { $_.PSChildName -match '^\\d{4}$' }; - foreach($key in $gpuKeys) { - $props = Get-ItemProperty $key.PSPath -ErrorAction SilentlyContinue; - if($props.DriverDesc -and $props.'HardwareInformation.qwMemorySize' -and $activeGpus -contains $props.DriverDesc) { - $vramBytes = $props.'HardwareInformation.qwMemorySize'; - $vramGB = [math]::Round($vramBytes/1GB, 2); - if($vramGB -ge 1) { - Write-Output "$($props.DriverDesc)|$vramGB"; - } - } - }`, - ], - { - timeout: 10000, - reject: false, - } + const graphics = await siGraphics(); + + const discreteControllers = graphics.controllers.filter( + (controller) => controller.vram && controller.vram >= 1024 ); - if (!stdout.trim()) { - return []; - } - const gpus: GPUData[] = []; - const lines = stdout.trim().split('\n'); - const seenVram = new Set(); - for (const line of lines) { - const parts = line.trim().split('|'); - if (parts.length === 2) { - const vramGB = parseFloat(parts[1]); - if (vramGB > 0 && !seenVram.has(vramGB)) { - seenVram.add(vramGB); - gpus.push({ - usage: 0, - memoryUsed: 0, - memoryTotal: vramGB, - temperature: undefined, - }); - } + for (const controller of discreteControllers) { + if (controller.vram) { + const vramGB = parseFloat((controller.vram / 1024).toFixed(2)); + gpus.push({ + usage: 0, + memoryUsed: 0, + memoryTotal: vramGB, + temperature: undefined, + }); } } @@ -250,10 +223,17 @@ export async function detectGPUViaSI() { let hasNVIDIA = false; const gpuInfo: string[] = []; - const discreteControllers = graphics.controllers.filter( - (controller) => - controller.busAddress && isDiscreteBusAddress(controller.busAddress) - ); + const discreteControllers = graphics.controllers.filter((controller) => { + if (platform === 'linux' && controller.busAddress) { + return isDiscreteBusAddress(controller.busAddress); + } + + if (platform === 'win32') { + return controller.vram && controller.vram >= 1024; + } + + return false; + }); for (const controller of discreteControllers) { if ( @@ -268,13 +248,13 @@ export async function detectGPUViaSI() { } if (controller.model) { - gpuInfo.push(controller.model); + gpuInfo.push(formatDeviceName(controller.model)); } } return { hasAMD, hasNVIDIA, - gpuInfo: gpuInfo.length > 0 ? gpuInfo : ['No GPU information available'], + gpuInfo: gpuInfo.length > 0 ? gpuInfo : [], }; } diff --git a/src/utils/node/vulkan.ts b/src/utils/node/vulkan.ts index 8744364..737b4a6 100644 --- a/src/utils/node/vulkan.ts +++ b/src/utils/node/vulkan.ts @@ -151,13 +151,33 @@ export async function detectLinuxGPUViaVulkan() { return { hasAMD, hasNVIDIA, - gpuInfo: gpuInfo.length > 0 ? gpuInfo : ['No GPU information available'], + gpuInfo: gpuInfo.length > 0 ? gpuInfo : [], }; } catch { return { hasAMD: false, hasNVIDIA: false, - gpuInfo: ['GPU detection failed'], + gpuInfo: [], }; } } + +export async function detectVulkan() { + try { + const vulkanInfo = await getVulkanInfo(); + + const devices: string[] = []; + + for (const gpu of vulkanInfo.discreteGPUs) { + devices.push(formatDeviceName(gpu.deviceName)); + } + + return { + supported: devices.length > 0, + devices, + version: vulkanInfo.apiVersion || 'Unknown', + }; + } catch { + return { supported: false, devices: [], version: 'Unknown' }; + } +} diff --git a/src/utils/systemInfo.ts b/src/utils/systemInfo.ts index dc81ef7..131a4d1 100644 --- a/src/utils/systemInfo.ts +++ b/src/utils/systemInfo.ts @@ -72,7 +72,7 @@ export const createDriverItems = (hardwareInfo: HardwareInfo) => { if (gpuCapabilities.cuda.driverVersion) { items.push({ - label: 'NVIDIA Driver', + label: 'NVIDIA', value: gpuCapabilities.cuda.driverVersion, }); } @@ -88,7 +88,7 @@ export const createDriverItems = (hardwareInfo: HardwareInfo) => { if (gpuCapabilities.rocm.driverVersion) { items.push({ - label: 'AMD Driver', + label: 'AMD', value: gpuCapabilities.rocm.driverVersion, }); }