mirror of
https://github.com/lone-cloud/gerbil
synced 2026-06-03 09:33:10 -07:00
better discrete device filtering
This commit is contained in:
parent
ddf42c41db
commit
b2533659c2
9 changed files with 195 additions and 80 deletions
|
|
@ -1,5 +1,6 @@
|
|||
import { Text, Group, Badge, Box } from '@mantine/core';
|
||||
import type { BackendOption } from '@/types';
|
||||
import { GPUDevice } from '@/types/hardware';
|
||||
|
||||
type BackendSelectItemProps = Omit<BackendOption, 'value'>;
|
||||
|
||||
|
|
@ -8,9 +9,7 @@ export const BackendSelectItem = ({
|
|||
devices,
|
||||
disabled = false,
|
||||
}: BackendSelectItemProps) => {
|
||||
const renderDeviceName = (
|
||||
device: string | { name: string; isIntegrated: boolean }
|
||||
) => {
|
||||
const renderDeviceName = (device: string | GPUDevice) => {
|
||||
const deviceName = typeof device === 'string' ? device : device.name;
|
||||
return deviceName.length > 25
|
||||
? `${deviceName.slice(0, 25)}...`
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ export const GpuDeviceSelector = ({
|
|||
|
||||
const getDiscreteDeviceCount = () => {
|
||||
if (!selectedBackend?.devices) return 0;
|
||||
if (backend === 'clblast') {
|
||||
if (backend === 'clblast' || backend === 'vulkan' || backend === 'rocm') {
|
||||
return selectedBackend.devices.filter(
|
||||
(device) => typeof device === 'string' || !device.isIntegrated
|
||||
).length;
|
||||
|
|
@ -66,6 +66,25 @@ export const GpuDeviceSelector = ({
|
|||
);
|
||||
}
|
||||
|
||||
if (backend === 'vulkan' || backend === 'rocm') {
|
||||
const discreteDeviceOptions = 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}: ${deviceName}`,
|
||||
};
|
||||
})
|
||||
.filter(
|
||||
(option): option is NonNullable<typeof option> => option !== null
|
||||
);
|
||||
|
||||
return [{ value: 'all', label: 'All GPUs' }, ...discreteDeviceOptions];
|
||||
}
|
||||
|
||||
return [
|
||||
{ value: 'all', label: 'All GPUs' },
|
||||
...selectedBackend.devices.map((device, index) => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { useEffect, useState, useCallback, useMemo } from 'react';
|
||||
import type { BackendOption, BackendSupport } from '@/types';
|
||||
import { CPUCapabilities, GPUDevice } from '@/types/hardware';
|
||||
|
||||
export interface Warning {
|
||||
type: 'warning' | 'info';
|
||||
|
|
@ -46,7 +47,9 @@ const checkModelWarnings = (
|
|||
|
||||
interface GpuCapabilities {
|
||||
cuda: { devices: readonly string[] };
|
||||
rocm: { devices: readonly string[] };
|
||||
rocm: { devices: readonly GPUDevice[] };
|
||||
vulkan: { devices: readonly GPUDevice[] };
|
||||
clblast: { devices: readonly GPUDevice[] };
|
||||
}
|
||||
|
||||
interface GpuInfo {
|
||||
|
|
@ -155,11 +158,9 @@ const checkCpuWarnings = (
|
|||
|
||||
const checkBackendWarnings = async (params?: {
|
||||
backend: string;
|
||||
cpuCapabilities: {
|
||||
devices: string[];
|
||||
} | null;
|
||||
cpuCapabilities: CPUCapabilities | null;
|
||||
availableBackends: BackendOption[];
|
||||
}): Promise<Warning[]> => {
|
||||
}) => {
|
||||
const warnings: Warning[] = [];
|
||||
|
||||
const [backendSupport, gpuCapabilities, gpuInfo] = await Promise.all([
|
||||
|
|
|
|||
|
|
@ -11,11 +11,12 @@ import type {
|
|||
GPUCapabilities,
|
||||
BasicGPUInfo,
|
||||
GPUMemoryInfo,
|
||||
GPUDevice,
|
||||
} from '@/types/hardware';
|
||||
import { execa } from 'execa';
|
||||
import { formatDeviceName } from '@/utils/format';
|
||||
import { platform } from 'process';
|
||||
import { getVulkanInfo, detectLinuxGPUViaVulkan } from '@/utils/node/vulkan';
|
||||
import { getVulkanInfo, detectGPUViaVulkan } from '@/utils/node/vulkan';
|
||||
|
||||
const COMMON_EXEC_OPTIONS = {
|
||||
timeout: 3000,
|
||||
|
|
@ -35,11 +36,14 @@ export async function detectCPU() {
|
|||
const result = await safeExecute(async () => {
|
||||
const cpu = await siCpu();
|
||||
|
||||
const devices: string[] = [];
|
||||
const devices: { name: string; detailedName: string }[] = [];
|
||||
if (cpu.brand) {
|
||||
devices.push(
|
||||
`${formatDeviceName(cpu.brand)} (${cpu.cores} cores) @ ${cpu.speed} GHz`
|
||||
);
|
||||
const name = formatDeviceName(cpu.brand);
|
||||
|
||||
devices.push({
|
||||
name,
|
||||
detailedName: `${name} (${cpu.cores} cores) @ ${cpu.speed} GHz`,
|
||||
});
|
||||
}
|
||||
|
||||
const capabilities = {
|
||||
|
|
@ -50,11 +54,10 @@ export async function detectCPU() {
|
|||
return capabilities;
|
||||
}, 'CPU detection failed');
|
||||
|
||||
const fallbackCapabilities = {
|
||||
cpuCapabilitiesCache = result || {
|
||||
devices: [],
|
||||
};
|
||||
|
||||
cpuCapabilitiesCache = result || fallbackCapabilities;
|
||||
return cpuCapabilitiesCache;
|
||||
}
|
||||
|
||||
|
|
@ -64,7 +67,7 @@ export async function detectGPU() {
|
|||
}
|
||||
|
||||
const result = await safeExecute(
|
||||
() => detectLinuxGPUViaVulkan(),
|
||||
() => detectGPUViaVulkan(),
|
||||
'GPU detection failed'
|
||||
);
|
||||
|
||||
|
|
@ -101,10 +104,15 @@ async function detectVulkan() {
|
|||
try {
|
||||
const vulkanInfo = await getVulkanInfo();
|
||||
|
||||
const devices: string[] = [];
|
||||
const devices: GPUDevice[] = [];
|
||||
|
||||
for (const gpu of vulkanInfo.discreteGPUs) {
|
||||
devices.push(formatDeviceName(gpu.deviceName));
|
||||
for (const gpu of vulkanInfo.allGPUs) {
|
||||
const isIntegrated = gpu.isIntegrated;
|
||||
|
||||
devices.push({
|
||||
name: isIntegrated ? gpu.deviceName : formatDeviceName(gpu.deviceName),
|
||||
isIntegrated,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
@ -187,21 +195,56 @@ export async function detectROCm() {
|
|||
const { stdout } = await execa(rocminfoCommand, [], COMMON_EXEC_OPTIONS);
|
||||
|
||||
if (stdout.trim()) {
|
||||
const devices: string[] = [];
|
||||
const devices: GPUDevice[] = [];
|
||||
|
||||
if (platform === 'win32') {
|
||||
const lines = stdout.split('\n');
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
|
||||
for (const line of lines) {
|
||||
const trimmedLine = line.trim();
|
||||
if (trimmedLine.startsWith('Name:')) {
|
||||
const name = trimmedLine.split('Name:')[1]?.trim();
|
||||
if (
|
||||
name &&
|
||||
!name.toLowerCase().includes('cpu') &&
|
||||
!devices.includes(formatDeviceName(name))
|
||||
) {
|
||||
devices.push(formatDeviceName(name));
|
||||
if (line.includes('Marketing Name:')) {
|
||||
const name = line.split('Marketing Name:')[1]?.trim();
|
||||
if (name && !name.toLowerCase().includes('cpu')) {
|
||||
let deviceType = '';
|
||||
|
||||
const searchRangeLines = 20;
|
||||
const searchStartIndex = Math.max(0, i - searchRangeLines);
|
||||
const searchEndIndex = Math.min(
|
||||
lines.length,
|
||||
i + searchRangeLines
|
||||
);
|
||||
|
||||
for (
|
||||
let searchIndex = searchStartIndex;
|
||||
searchIndex < searchEndIndex;
|
||||
searchIndex++
|
||||
) {
|
||||
if (lines[searchIndex].includes('Device Type:')) {
|
||||
deviceType =
|
||||
lines[searchIndex].split('Device Type:')[1]?.trim() || '';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (deviceType !== 'CPU') {
|
||||
let isIntegrated = true;
|
||||
try {
|
||||
const vulkanInfo = await getVulkanInfo();
|
||||
const matchingGPU = vulkanInfo.allGPUs.find(
|
||||
(gpu) =>
|
||||
gpu.deviceName.includes(name) ||
|
||||
name.includes(gpu.deviceName)
|
||||
);
|
||||
isIntegrated = matchingGPU ? matchingGPU.isIntegrated : false;
|
||||
} catch {
|
||||
isIntegrated = false;
|
||||
}
|
||||
|
||||
devices.push({
|
||||
name: isIntegrated ? name : formatDeviceName(name),
|
||||
isIntegrated,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -235,7 +278,24 @@ export async function detectROCm() {
|
|||
}
|
||||
|
||||
if (deviceType !== 'CPU') {
|
||||
devices.push(formatDeviceName(name));
|
||||
// Check if integrated by cross-referencing with vulkan GPU list
|
||||
let isIntegrated = true;
|
||||
try {
|
||||
const vulkanInfo = await getVulkanInfo();
|
||||
const matchingGPU = vulkanInfo.allGPUs.find(
|
||||
(gpu) =>
|
||||
gpu.deviceName.includes(name) ||
|
||||
name.includes(gpu.deviceName)
|
||||
);
|
||||
isIntegrated = matchingGPU ? matchingGPU.isIntegrated : false;
|
||||
} catch {
|
||||
isIntegrated = false;
|
||||
}
|
||||
|
||||
devices.push({
|
||||
name: isIntegrated ? name : formatDeviceName(name),
|
||||
isIntegrated,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -301,8 +361,8 @@ export async function detectROCm() {
|
|||
try {
|
||||
const vulkanInfo = await getVulkanInfo();
|
||||
|
||||
for (const gpu of vulkanInfo.discreteGPUs) {
|
||||
if (gpu.driverInfo) {
|
||||
for (const gpu of vulkanInfo.allGPUs) {
|
||||
if (gpu.driverInfo && !gpu.isIntegrated) {
|
||||
driverVersion = gpu.driverInfo;
|
||||
break;
|
||||
}
|
||||
|
|
@ -324,7 +384,7 @@ export async function detectROCm() {
|
|||
}
|
||||
|
||||
function parseClInfoOutput(output: string) {
|
||||
const devices: { name: string; isIntegrated: boolean }[] = [];
|
||||
const devices: GPUDevice[] = [];
|
||||
const lines = output.split('\n');
|
||||
|
||||
let currentPlatform = '';
|
||||
|
|
@ -342,9 +402,11 @@ function parseClInfoOutput(output: string) {
|
|||
const computeUnits = findComputeUnitsInClInfo(lines, i);
|
||||
|
||||
if (deviceName && currentPlatform) {
|
||||
const isIntegrated = !isDiscreteGPU(computeUnits);
|
||||
|
||||
devices.push({
|
||||
name: formatDeviceName(deviceName),
|
||||
isIntegrated: !isDiscreteGPU(computeUnits),
|
||||
name: isIntegrated ? deviceName : formatDeviceName(deviceName),
|
||||
isIntegrated,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ export async function getAvailableBackends(includeDisabled = false) {
|
|||
backends.push({
|
||||
value: 'cpu',
|
||||
label: 'CPU',
|
||||
devices: cpuCapabilities?.devices,
|
||||
devices: cpuCapabilities?.devices.map((device) => device.name) || [],
|
||||
disabled: false,
|
||||
});
|
||||
|
||||
|
|
|
|||
18
src/types/hardware.d.ts
vendored
18
src/types/hardware.d.ts
vendored
|
|
@ -1,5 +1,5 @@
|
|||
export interface CPUCapabilities {
|
||||
devices: string[];
|
||||
devices: { name: string; detailedName: string }[];
|
||||
}
|
||||
|
||||
export interface GPUMemoryInfo {
|
||||
|
|
@ -12,6 +12,11 @@ export interface SystemMemoryInfo {
|
|||
type?: string;
|
||||
}
|
||||
|
||||
export interface GPUDevice {
|
||||
readonly name: string;
|
||||
readonly isIntegrated: boolean;
|
||||
}
|
||||
|
||||
export interface GPUCapabilities {
|
||||
cuda: {
|
||||
readonly devices: readonly string[];
|
||||
|
|
@ -19,16 +24,16 @@ export interface GPUCapabilities {
|
|||
readonly driverVersion?: string;
|
||||
};
|
||||
rocm: {
|
||||
readonly devices: readonly string[];
|
||||
readonly devices: readonly GPUDevice[];
|
||||
readonly version?: string;
|
||||
readonly driverVersion?: string;
|
||||
};
|
||||
vulkan: {
|
||||
readonly devices: readonly string[];
|
||||
readonly devices: readonly GPUDevice[];
|
||||
readonly version?: string;
|
||||
};
|
||||
clblast: {
|
||||
readonly devices: readonly CLBlastDevice[];
|
||||
readonly devices: readonly GPUDevice[];
|
||||
readonly version?: string;
|
||||
};
|
||||
}
|
||||
|
|
@ -39,11 +44,6 @@ export interface BasicGPUInfo {
|
|||
gpuInfo: string[];
|
||||
}
|
||||
|
||||
export interface CLBlastDevice {
|
||||
readonly name: string;
|
||||
readonly isIntegrated: boolean;
|
||||
}
|
||||
|
||||
export interface HardwareDetectionResult {
|
||||
readonly supported: boolean;
|
||||
readonly devices: readonly string[];
|
||||
|
|
|
|||
7
src/types/index.d.ts
vendored
7
src/types/index.d.ts
vendored
|
|
@ -1,3 +1,5 @@
|
|||
import { GPUDevice } from './hardware';
|
||||
|
||||
export interface ConfigFile {
|
||||
name: string;
|
||||
path: string;
|
||||
|
|
@ -72,10 +74,7 @@ export interface SelectOption {
|
|||
}
|
||||
|
||||
export interface BackendOption extends SelectOption {
|
||||
readonly devices?: readonly (
|
||||
| string
|
||||
| { name: string; isIntegrated: boolean }
|
||||
)[];
|
||||
readonly devices?: readonly (string | GPUDevice)[];
|
||||
readonly disabled?: boolean;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,12 +3,13 @@ import { execa } from 'execa';
|
|||
import { formatDeviceName } from '@/utils/format';
|
||||
|
||||
let vulkanInfoCache: {
|
||||
discreteGPUs: {
|
||||
allGPUs: {
|
||||
deviceName: string;
|
||||
driverInfo?: string;
|
||||
apiVersion?: string;
|
||||
hasAMD: boolean;
|
||||
hasNVIDIA: boolean;
|
||||
isIntegrated: boolean;
|
||||
}[];
|
||||
apiVersion?: string;
|
||||
} | null = null;
|
||||
|
|
@ -25,19 +26,20 @@ export async function getVulkanInfo() {
|
|||
reject: false,
|
||||
});
|
||||
|
||||
const discreteGPUs: {
|
||||
const allGPUs: {
|
||||
deviceName: string;
|
||||
driverInfo?: string;
|
||||
apiVersion?: string;
|
||||
hasAMD: boolean;
|
||||
hasNVIDIA: boolean;
|
||||
isIntegrated: boolean;
|
||||
}[] = [];
|
||||
let globalApiVersion: string | undefined;
|
||||
|
||||
if (stdout.trim()) {
|
||||
const lines = stdout.split('\n');
|
||||
let foundDiscreteGPU = false;
|
||||
let currentGPU: (typeof discreteGPUs)[0] | null = null;
|
||||
let foundGPU = false;
|
||||
let currentGPU: (typeof allGPUs)[0] | null = null;
|
||||
|
||||
for (const line of lines) {
|
||||
if (
|
||||
|
|
@ -51,15 +53,22 @@ export async function getVulkanInfo() {
|
|||
}
|
||||
}
|
||||
|
||||
if (line.includes('PHYSICAL_DEVICE_TYPE_DISCRETE_GPU')) {
|
||||
foundDiscreteGPU = true;
|
||||
if (
|
||||
line.includes('PHYSICAL_DEVICE_TYPE_DISCRETE_GPU') ||
|
||||
line.includes('PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU')
|
||||
) {
|
||||
foundGPU = true;
|
||||
const isIntegrated = line.includes(
|
||||
'PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU'
|
||||
);
|
||||
currentGPU = {
|
||||
deviceName: '',
|
||||
hasAMD: false,
|
||||
hasNVIDIA: false,
|
||||
isIntegrated,
|
||||
};
|
||||
} else if (
|
||||
foundDiscreteGPU &&
|
||||
foundGPU &&
|
||||
currentGPU &&
|
||||
line.includes('deviceName') &&
|
||||
line.includes('=')
|
||||
|
|
@ -79,17 +88,13 @@ export async function getVulkanInfo() {
|
|||
name.toLowerCase().includes('gtx');
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
foundDiscreteGPU &&
|
||||
currentGPU &&
|
||||
line.includes('driverInfo')
|
||||
) {
|
||||
} else if (foundGPU && currentGPU && line.includes('driverInfo')) {
|
||||
const mesaMatch = line.match(/Mesa\s+(.+)/);
|
||||
if (mesaMatch) {
|
||||
currentGPU.driverInfo = `Mesa ${mesaMatch[1].trim()}`;
|
||||
}
|
||||
} else if (
|
||||
foundDiscreteGPU &&
|
||||
foundGPU &&
|
||||
currentGPU &&
|
||||
line.includes('apiVersion') &&
|
||||
line.includes('=')
|
||||
|
|
@ -101,35 +106,35 @@ export async function getVulkanInfo() {
|
|||
globalApiVersion = match[1];
|
||||
}
|
||||
}
|
||||
} else if (foundDiscreteGPU && currentGPU && line.includes('GPU')) {
|
||||
} else if (foundGPU && currentGPU && line.includes('GPU')) {
|
||||
if (currentGPU.deviceName) {
|
||||
discreteGPUs.push(currentGPU);
|
||||
allGPUs.push(currentGPU);
|
||||
}
|
||||
foundDiscreteGPU = false;
|
||||
foundGPU = false;
|
||||
currentGPU = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundDiscreteGPU && currentGPU && currentGPU.deviceName) {
|
||||
discreteGPUs.push(currentGPU);
|
||||
if (foundGPU && currentGPU && currentGPU.deviceName) {
|
||||
allGPUs.push(currentGPU);
|
||||
}
|
||||
}
|
||||
|
||||
vulkanInfoCache = {
|
||||
discreteGPUs,
|
||||
allGPUs,
|
||||
apiVersion: globalApiVersion,
|
||||
};
|
||||
|
||||
return vulkanInfoCache;
|
||||
} catch {
|
||||
vulkanInfoCache = {
|
||||
discreteGPUs: [],
|
||||
allGPUs: [],
|
||||
};
|
||||
return vulkanInfoCache;
|
||||
}
|
||||
}
|
||||
|
||||
export async function detectLinuxGPUViaVulkan() {
|
||||
export async function detectGPUViaVulkan() {
|
||||
try {
|
||||
const vulkanInfo = await getVulkanInfo();
|
||||
|
||||
|
|
@ -137,7 +142,7 @@ export async function detectLinuxGPUViaVulkan() {
|
|||
let hasNVIDIA = false;
|
||||
const gpuInfo: string[] = [];
|
||||
|
||||
for (const gpu of vulkanInfo.discreteGPUs) {
|
||||
for (const gpu of vulkanInfo.allGPUs.filter((g) => !g.isIntegrated)) {
|
||||
gpuInfo.push(formatDeviceName(gpu.deviceName));
|
||||
|
||||
if (gpu.hasAMD) {
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ export const createHardwareItems = (hardwareInfo: HardwareInfo) => [
|
|||
label: 'CPU',
|
||||
value:
|
||||
hardwareInfo.cpu.devices.length > 0
|
||||
? hardwareInfo.cpu.devices[0]
|
||||
? hardwareInfo.cpu.devices[0].detailedName
|
||||
: 'Unknown',
|
||||
},
|
||||
{
|
||||
|
|
@ -147,12 +147,42 @@ export const createHardwareItems = (hardwareInfo: HardwareInfo) => [
|
|||
}`
|
||||
: 'Detecting...',
|
||||
},
|
||||
...(hardwareInfo.gpu.gpuInfo.length > 0
|
||||
? hardwareInfo.gpu.gpuInfo.map((gpu, index) => ({
|
||||
label: `GPU ${index > 0 ? index + 1 : ''}`.trim(),
|
||||
value: gpu,
|
||||
}))
|
||||
: [{ label: 'GPU', value: 'No GPU detected' }]),
|
||||
...(() => {
|
||||
const discreteGPUs = [];
|
||||
|
||||
if (hardwareInfo.gpuCapabilities.vulkan.devices.length > 0) {
|
||||
discreteGPUs.push(
|
||||
...hardwareInfo.gpuCapabilities.vulkan.devices.filter(
|
||||
(gpu) => !gpu.isIntegrated
|
||||
)
|
||||
);
|
||||
}
|
||||
if (hardwareInfo.gpuCapabilities.rocm.devices.length > 0) {
|
||||
discreteGPUs.push(
|
||||
...hardwareInfo.gpuCapabilities.rocm.devices.filter(
|
||||
(gpu) => !gpu.isIntegrated
|
||||
)
|
||||
);
|
||||
}
|
||||
if (hardwareInfo.gpuCapabilities.clblast.devices.length > 0) {
|
||||
discreteGPUs.push(
|
||||
...hardwareInfo.gpuCapabilities.clblast.devices.filter(
|
||||
(gpu) => !gpu.isIntegrated
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const uniqueDiscreteGPUs = discreteGPUs.filter(
|
||||
(gpu, index, arr) => arr.findIndex((g) => g.name === gpu.name) === index
|
||||
);
|
||||
|
||||
return uniqueDiscreteGPUs.length > 0
|
||||
? uniqueDiscreteGPUs.map((gpu, index) => ({
|
||||
label: `GPU ${index > 0 ? index + 1 : ''}`.trim(),
|
||||
value: gpu.name,
|
||||
}))
|
||||
: [{ label: 'GPU', value: 'No discrete GPU detected' }];
|
||||
})(),
|
||||
...(hardwareInfo.gpuMemory && hardwareInfo.gpuMemory.length > 0
|
||||
? hardwareInfo.gpuMemory.map((mem, index) => ({
|
||||
label: `VRAM ${index > 0 ? index + 1 : ''}`.trim(),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue