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

This commit is contained in:
Egor 2025-09-26 13:02:05 -07:00
parent 3652ce4686
commit 48338b9904
11 changed files with 219 additions and 183 deletions

View file

@ -7,31 +7,51 @@ export const BackendSelectItem = ({
label, label,
devices, devices,
disabled = false, disabled = false,
}: BackendSelectItemProps) => ( }: BackendSelectItemProps) => {
<Group justify="space-between" wrap="nowrap"> const renderDeviceName = (
<Box w={!disabled ? '3rem' : 'auto'}> device: string | { name: string; isIntegrated: boolean }
<Text size="sm" truncate> ) => {
{label} const deviceName = typeof device === 'string' ? device : device.name;
{disabled && ( return deviceName.length > 25
<Text component="span" size="xs" ml="xs"> ? `${deviceName.slice(0, 25)}...`
(Compatible devices not found) : deviceName;
</Text> };
)}
</Text> return (
</Box> <Group justify="space-between" wrap="nowrap">
{devices && devices.length > 0 && ( <Box w={!disabled ? '3.5rem' : 'auto'}>
<Group gap={4}> <Text size="sm" truncate>
{devices.slice(0, 2).map((device, index) => ( {label}
<Badge key={index} size="md" variant="light" color="blue"> {disabled && (
{device.length > 25 ? `${device.slice(0, 25)}...` : device} <Text component="span" size="xs" ml="xs">
</Badge> (Compatible devices not found)
))} </Text>
{devices.length > 2 && ( )}
<Badge size="md" variant="light" color="gray"> </Text>
+{devices.length - 2} </Box>
</Badge> {devices &&
)} devices.length > 0 &&
</Group> (() => {
)} const discreteDevices = devices.filter(
</Group> (device) => typeof device === 'string' || !device.isIntegrated
); );
return (
discreteDevices.length > 0 && (
<Group gap={4}>
{discreteDevices.slice(0, 2).map((device, index) => (
<Badge key={index} size="md" variant="light" color="blue">
{renderDeviceName(device)}
</Badge>
))}
{discreteDevices.length > 2 && (
<Badge size="md" variant="light" color="gray">
+{discreteDevices.length - 2}
</Badge>
)}
</Group>
)
);
})()}
</Group>
);
};

View file

@ -25,8 +25,18 @@ export const GpuDeviceSelector = ({
backend === 'rocm' || backend === 'rocm' ||
backend === 'vulkan' || backend === 'vulkan' ||
backend === 'clblast'; 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 = const showTensorSplit =
(backend === 'cuda' || backend === 'rocm' || backend === 'vulkan') && (backend === 'cuda' || backend === 'rocm' || backend === 'vulkan') &&
hasMultipleDevices && hasMultipleDevices &&
@ -36,19 +46,42 @@ export const GpuDeviceSelector = ({
return null; return null;
} }
const deviceOptions = const deviceOptions = (() => {
backend === 'clblast' if (!selectedBackend?.devices) return [];
? selectedBackend.devices!.map((device, index) => ({
value: index.toString(), if (backend === 'clblast') {
label: `GPU ${index}: ${device}`, return selectedBackend.devices
})) .map((device, index) => {
: [ if (typeof device === 'object' && device.isIntegrated) {
{ value: 'all', label: 'All GPUs' }, return null;
...selectedBackend.devices!.map((device, index) => ({ }
const deviceName = typeof device === 'string' ? device : device.name;
return {
value: index.toString(), value: index.toString(),
label: `GPU ${index}: ${device}`, label: `GPU ${index}: ${deviceName}`,
})), };
]; })
.filter(
(option): option is NonNullable<typeof option> => 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 ( return (
<div> <div>

View file

@ -75,8 +75,6 @@ export const LaunchScreen = ({ onLaunch }: LaunchScreenProps) => {
model, model,
sdmodel, sdmodel,
backend, backend,
noavx2,
failsafe,
configLoaded, configLoaded,
}); });

View file

@ -85,10 +85,14 @@ const buildModelArgs = (
const buildConfigArgs = (isImageMode: boolean, launchArgs: LaunchArgs) => { const buildConfigArgs = (isImageMode: boolean, launchArgs: LaunchArgs) => {
const args: string[] = []; const args: string[] = [];
if (launchArgs.autoGpuLayers) { const isGpuBackend = launchArgs.backend && launchArgs.backend !== 'cpu';
args.push('--gpulayers', '-1');
} else if (launchArgs.gpuLayers > 0) { if (isGpuBackend) {
args.push('--gpulayers', launchArgs.gpuLayers.toString()); if (launchArgs.autoGpuLayers) {
args.push('--gpulayers', '-1');
} else if (launchArgs.gpuLayers > 0) {
args.push('--gpulayers', launchArgs.gpuLayers.toString());
}
} }
if (launchArgs.contextSize) { if (launchArgs.contextSize) {
@ -193,6 +197,9 @@ const buildBackendArgs = (launchArgs: LaunchArgs) => {
const args: string[] = []; const args: string[] = [];
if (!launchArgs.backend || launchArgs.backend === 'cpu') { if (!launchArgs.backend || launchArgs.backend === 'cpu') {
if (launchArgs.backend === 'cpu') {
args.push('--usecpu');
}
return args; return args;
} }

View file

@ -10,8 +10,6 @@ interface UseWarningsProps {
model: string; model: string;
sdmodel: string; sdmodel: string;
backend?: string; backend?: string;
noavx2?: boolean;
failsafe?: boolean;
configLoaded?: boolean; configLoaded?: boolean;
} }
@ -133,9 +131,6 @@ const checkVramWarnings = async (backend: string): Promise<Warning[]> => {
const checkCpuWarnings = ( const checkCpuWarnings = (
backend: string, backend: string,
cpuCapabilities: { avx: boolean; avx2: boolean },
noavx2: boolean,
failsafe: boolean,
availableBackends: BackendOption[] availableBackends: BackendOption[]
) => { ) => {
const warnings: Warning[] = []; const warnings: Warning[] = [];
@ -144,22 +139,6 @@ const checkCpuWarnings = (
return warnings; 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 ( if (
availableBackends.length > 0 && availableBackends.length > 0 &&
availableBackends.some((b) => b.value === 'cpu') availableBackends.some((b) => b.value === 'cpu')
@ -177,11 +156,8 @@ const checkCpuWarnings = (
const checkBackendWarnings = async (params?: { const checkBackendWarnings = async (params?: {
backend: string; backend: string;
cpuCapabilities: { cpuCapabilities: {
avx: boolean; devices: string[];
avx2: boolean;
} | null; } | null;
noavx2: boolean;
failsafe: boolean;
availableBackends: BackendOption[]; availableBackends: BackendOption[];
}): Promise<Warning[]> => { }): Promise<Warning[]> => {
const warnings: Warning[] = []; const warnings: Warning[] = [];
@ -204,20 +180,13 @@ const checkBackendWarnings = async (params?: {
warnings.push(...gpuWarnings); warnings.push(...gpuWarnings);
if (params) { if (params) {
const { backend, cpuCapabilities, noavx2, failsafe, availableBackends } = const { backend, cpuCapabilities, availableBackends } = params;
params;
const vramWarnings = await checkVramWarnings(backend); const vramWarnings = await checkVramWarnings(backend);
warnings.push(...vramWarnings); warnings.push(...vramWarnings);
if (cpuCapabilities) { if (cpuCapabilities) {
const cpuWarnings = checkCpuWarnings( const cpuWarnings = checkCpuWarnings(backend, availableBackends);
backend,
cpuCapabilities,
noavx2,
failsafe,
availableBackends
);
warnings.push(...cpuWarnings); warnings.push(...cpuWarnings);
} }
} }
@ -229,8 +198,6 @@ export const useWarnings = ({
model, model,
sdmodel, sdmodel,
backend, backend,
noavx2 = false,
failsafe = false,
configLoaded = false, configLoaded = false,
}: UseWarningsProps) => { }: UseWarningsProps) => {
const [backendWarnings, setBackendWarnings] = useState<Warning[]>([]); const [backendWarnings, setBackendWarnings] = useState<Warning[]>([]);
@ -251,21 +218,14 @@ export const useWarnings = ({
window.electronAPI.kobold.getAvailableBackends(), window.electronAPI.kobold.getAvailableBackends(),
]); ]);
const cpuCapabilities = {
avx: cpuCapabilitiesResult.avx,
avx2: cpuCapabilitiesResult.avx2,
};
const result = await checkBackendWarnings({ const result = await checkBackendWarnings({
backend, backend,
cpuCapabilities, cpuCapabilities: cpuCapabilitiesResult,
noavx2,
failsafe,
availableBackends, availableBackends,
}); });
setBackendWarnings(result); setBackendWarnings(result);
}, [backend, noavx2, failsafe]); }, [backend]);
useEffect(() => { useEffect(() => {
updateBackendWarnings(); updateBackendWarnings();

View file

@ -1,7 +1,6 @@
/* eslint-disable no-comments/disallowComments */ /* eslint-disable no-comments/disallowComments */
import { import {
cpu as siCpu, cpu as siCpu,
cpuFlags,
mem as siMem, mem as siMem,
memLayout as siMemLayout, memLayout as siMemLayout,
} from 'systeminformation'; } from 'systeminformation';
@ -16,7 +15,11 @@ import type {
import { execa } from 'execa'; import { execa } from 'execa';
import { formatDeviceName } from '@/utils/format'; import { formatDeviceName } from '@/utils/format';
import { platform } from 'process'; import { platform } from 'process';
import { getVulkanInfo, detectLinuxGPUViaVulkan } from '@/utils/node/vulkan'; import {
getVulkanInfo,
detectLinuxGPUViaVulkan,
detectVulkan,
} from '@/utils/node/vulkan';
const COMMON_EXEC_OPTIONS = { const COMMON_EXEC_OPTIONS = {
timeout: 3000, timeout: 3000,
@ -34,7 +37,7 @@ export async function detectCPU() {
} }
const result = await safeExecute(async () => { const result = await safeExecute(async () => {
const [cpu, flags] = await Promise.all([siCpu(), cpuFlags()]); const cpu = await siCpu();
const devices: string[] = []; const devices: string[] = [];
if (cpu.brand) { 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 = { const capabilities = {
avx,
avx2,
devices, devices,
}; };
@ -57,8 +55,6 @@ export async function detectCPU() {
}, 'CPU detection failed'); }, 'CPU detection failed');
const fallbackCapabilities = { const fallbackCapabilities = {
avx: false,
avx2: false,
devices: [], devices: [],
}; };
@ -74,15 +70,15 @@ export async function detectGPU() {
const result = await safeExecute(async () => { const result = await safeExecute(async () => {
if (platform === 'linux') { if (platform === 'linux') {
return detectLinuxGPUViaVulkan(); return detectLinuxGPUViaVulkan();
} else {
return detectGPUViaSI();
} }
return detectGPUViaSI();
}, 'GPU detection failed'); }, 'GPU detection failed');
const fallbackGPUInfo = { const fallbackGPUInfo = {
hasAMD: false, hasAMD: false,
hasNVIDIA: false, hasNVIDIA: false,
gpuInfo: ['GPU detection failed'], gpuInfo: [],
}; };
basicGPUInfoCache = result || fallbackGPUInfo; 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) { function parseClInfoOutput(output: string) {
const devices: string[] = []; const devices: { name: string; isIntegrated: boolean }[] = [];
const lines = output.split('\n'); const lines = output.split('\n');
let currentPlatform = ''; let currentPlatform = '';
@ -353,9 +329,13 @@ function parseClInfoOutput(output: string) {
if (line.includes('Device Type:') && line.includes('GPU')) { if (line.includes('Device Type:') && line.includes('GPU')) {
const deviceName = findDeviceNameInClInfo(lines, i); const deviceName = findDeviceNameInClInfo(lines, i);
const computeUnits = findComputeUnitsInClInfo(lines, i);
if (deviceName && currentPlatform) { 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 ''; 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() { async function detectCLBlast() {
try { try {
const { stdout } = await execa('clinfo', [], COMMON_EXEC_OPTIONS); const { stdout } = await execa('clinfo', [], COMMON_EXEC_OPTIONS);

View file

@ -1,6 +1,4 @@
export interface CPUCapabilities { export interface CPUCapabilities {
avx: boolean;
avx2: boolean;
devices: string[]; devices: string[];
} }
@ -34,7 +32,7 @@ export interface GPUCapabilities {
}; };
clblast: { clblast: {
readonly supported: boolean; readonly supported: boolean;
readonly devices: readonly string[]; readonly devices: readonly CLBlastDevice[];
readonly version?: string; readonly version?: string;
}; };
} }
@ -45,6 +43,11 @@ export interface BasicGPUInfo {
gpuInfo: string[]; gpuInfo: string[];
} }
export interface CLBlastDevice {
readonly name: string;
readonly isIntegrated: boolean;
}
export interface HardwareDetectionResult { export interface HardwareDetectionResult {
readonly supported: boolean; readonly supported: boolean;
readonly devices: readonly string[]; readonly devices: readonly string[];

View file

@ -72,7 +72,10 @@ export interface SelectOption {
} }
export interface BackendOption extends SelectOption { export interface BackendOption extends SelectOption {
readonly devices?: readonly string[]; readonly devices?: readonly (
| string
| { name: string; isIntegrated: boolean }
)[];
readonly disabled?: boolean; readonly disabled?: boolean;
} }

View file

@ -1,8 +1,8 @@
import { readFile, readdir } from 'fs/promises'; import { readFile, readdir } from 'fs/promises';
import { join } from 'path'; import { join } from 'path';
import { platform } from 'process'; import { platform } from 'process';
import { execa } from 'execa';
import { graphics as siGraphics } from 'systeminformation'; import { graphics as siGraphics } from 'systeminformation';
import { formatDeviceName } from '../format';
interface CachedGPUInfo { interface CachedGPUInfo {
devicePath: string; devicePath: string;
@ -167,50 +167,23 @@ async function getLinuxGPUData() {
async function getWindowsGPUData() { async function getWindowsGPUData() {
try { try {
const { stdout } = await execa( const graphics = await siGraphics();
'powershell',
[ const discreteControllers = graphics.controllers.filter(
'-Command', (controller) => controller.vram && controller.vram >= 1024
`$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,
}
); );
if (!stdout.trim()) {
return [];
}
const gpus: GPUData[] = []; const gpus: GPUData[] = [];
const lines = stdout.trim().split('\n');
const seenVram = new Set<number>();
for (const line of lines) { for (const controller of discreteControllers) {
const parts = line.trim().split('|'); if (controller.vram) {
if (parts.length === 2) { const vramGB = parseFloat((controller.vram / 1024).toFixed(2));
const vramGB = parseFloat(parts[1]); gpus.push({
if (vramGB > 0 && !seenVram.has(vramGB)) { usage: 0,
seenVram.add(vramGB); memoryUsed: 0,
gpus.push({ memoryTotal: vramGB,
usage: 0, temperature: undefined,
memoryUsed: 0, });
memoryTotal: vramGB,
temperature: undefined,
});
}
} }
} }
@ -250,10 +223,17 @@ export async function detectGPUViaSI() {
let hasNVIDIA = false; let hasNVIDIA = false;
const gpuInfo: string[] = []; const gpuInfo: string[] = [];
const discreteControllers = graphics.controllers.filter( const discreteControllers = graphics.controllers.filter((controller) => {
(controller) => if (platform === 'linux' && controller.busAddress) {
controller.busAddress && isDiscreteBusAddress(controller.busAddress) return isDiscreteBusAddress(controller.busAddress);
); }
if (platform === 'win32') {
return controller.vram && controller.vram >= 1024;
}
return false;
});
for (const controller of discreteControllers) { for (const controller of discreteControllers) {
if ( if (
@ -268,13 +248,13 @@ export async function detectGPUViaSI() {
} }
if (controller.model) { if (controller.model) {
gpuInfo.push(controller.model); gpuInfo.push(formatDeviceName(controller.model));
} }
} }
return { return {
hasAMD, hasAMD,
hasNVIDIA, hasNVIDIA,
gpuInfo: gpuInfo.length > 0 ? gpuInfo : ['No GPU information available'], gpuInfo: gpuInfo.length > 0 ? gpuInfo : [],
}; };
} }

View file

@ -151,13 +151,33 @@ export async function detectLinuxGPUViaVulkan() {
return { return {
hasAMD, hasAMD,
hasNVIDIA, hasNVIDIA,
gpuInfo: gpuInfo.length > 0 ? gpuInfo : ['No GPU information available'], gpuInfo: gpuInfo.length > 0 ? gpuInfo : [],
}; };
} catch { } catch {
return { return {
hasAMD: false, hasAMD: false,
hasNVIDIA: 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' };
}
}

View file

@ -72,7 +72,7 @@ export const createDriverItems = (hardwareInfo: HardwareInfo) => {
if (gpuCapabilities.cuda.driverVersion) { if (gpuCapabilities.cuda.driverVersion) {
items.push({ items.push({
label: 'NVIDIA Driver', label: 'NVIDIA',
value: gpuCapabilities.cuda.driverVersion, value: gpuCapabilities.cuda.driverVersion,
}); });
} }
@ -88,7 +88,7 @@ export const createDriverItems = (hardwareInfo: HardwareInfo) => {
if (gpuCapabilities.rocm.driverVersion) { if (gpuCapabilities.rocm.driverVersion) {
items.push({ items.push({
label: 'AMD Driver', label: 'AMD',
value: gpuCapabilities.rocm.driverVersion, value: gpuCapabilities.rocm.driverVersion,
}); });
} }