mirror of
https://github.com/lone-cloud/gerbil
synced 2026-06-03 09:33:10 -07:00
refactor vulkan detection
This commit is contained in:
parent
3ef91d1198
commit
3652ce4686
5 changed files with 209 additions and 205 deletions
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "gerbil",
|
||||
"productName": "Gerbil",
|
||||
"version": "1.6.0-beta-1",
|
||||
"version": "1.6.0",
|
||||
"description": "Run Large Language Models locally",
|
||||
"main": "out/main/index.js",
|
||||
"homepage": "./",
|
||||
|
|
@ -40,7 +40,7 @@
|
|||
"devDependencies": {
|
||||
"@eslint/js": "^9.36.0",
|
||||
"@types/node": "^24.5.2",
|
||||
"@types/react": "^19.1.13",
|
||||
"@types/react": "^19.1.14",
|
||||
"@types/react-dom": "^19.1.9",
|
||||
"@typescript-eslint/eslint-plugin": "^8.44.1",
|
||||
"@typescript-eslint/parser": "^8.44.1",
|
||||
|
|
|
|||
|
|
@ -2,12 +2,11 @@
|
|||
import {
|
||||
cpu as siCpu,
|
||||
cpuFlags,
|
||||
graphics as siGraphics,
|
||||
mem as siMem,
|
||||
memLayout as siMemLayout,
|
||||
} from 'systeminformation';
|
||||
import { safeExecute } from '@/utils/node/logging';
|
||||
import { getGPUData, isDiscreteBusAddress } from '@/utils/node/gpu';
|
||||
import { getGPUData, detectGPUViaSI } from '@/utils/node/gpu';
|
||||
import type {
|
||||
CPUCapabilities,
|
||||
GPUCapabilities,
|
||||
|
|
@ -17,6 +16,7 @@ import type {
|
|||
import { execa } from 'execa';
|
||||
import { formatDeviceName } from '@/utils/format';
|
||||
import { platform } from 'process';
|
||||
import { getVulkanInfo, detectLinuxGPUViaVulkan } from '@/utils/node/vulkan';
|
||||
|
||||
const COMMON_EXEC_OPTIONS = {
|
||||
timeout: 3000,
|
||||
|
|
@ -27,16 +27,6 @@ let cpuCapabilitiesCache: CPUCapabilities | null = null;
|
|||
let basicGPUInfoCache: BasicGPUInfo | null = null;
|
||||
let gpuCapabilitiesCache: GPUCapabilities | null = null;
|
||||
let gpuMemoryInfoCache: GPUMemoryInfo[] | null = null;
|
||||
let vulkanInfoCache: {
|
||||
discreteGPUs: {
|
||||
deviceName: string;
|
||||
driverInfo?: string;
|
||||
apiVersion?: string;
|
||||
hasAMD: boolean;
|
||||
hasNVIDIA: boolean;
|
||||
}[];
|
||||
apiVersion?: string;
|
||||
} | null = null;
|
||||
|
||||
export async function detectCPU() {
|
||||
if (cpuCapabilitiesCache) {
|
||||
|
|
@ -99,192 +89,6 @@ export async function detectGPU() {
|
|||
return basicGPUInfoCache;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
async function getVulkanInfo() {
|
||||
if (vulkanInfoCache) {
|
||||
return vulkanInfoCache;
|
||||
}
|
||||
|
||||
try {
|
||||
const { stdout } = await execa(
|
||||
'vulkaninfo',
|
||||
['--summary'],
|
||||
COMMON_EXEC_OPTIONS
|
||||
);
|
||||
|
||||
const discreteGPUs: {
|
||||
deviceName: string;
|
||||
driverInfo?: string;
|
||||
apiVersion?: string;
|
||||
hasAMD: boolean;
|
||||
hasNVIDIA: boolean;
|
||||
}[] = [];
|
||||
let globalApiVersion: string | undefined;
|
||||
|
||||
if (stdout.trim()) {
|
||||
const lines = stdout.split('\n');
|
||||
let foundDiscreteGPU = false;
|
||||
let currentGPU: (typeof discreteGPUs)[0] | null = null;
|
||||
|
||||
for (const line of lines) {
|
||||
if (
|
||||
!globalApiVersion &&
|
||||
line.includes('apiVersion') &&
|
||||
line.includes('=')
|
||||
) {
|
||||
const match = line.match(/=\s*(\d+\.\d+(?:\.\d+)?)/);
|
||||
if (match) {
|
||||
globalApiVersion = match[1];
|
||||
}
|
||||
}
|
||||
|
||||
if (line.includes('PHYSICAL_DEVICE_TYPE_DISCRETE_GPU')) {
|
||||
foundDiscreteGPU = true;
|
||||
currentGPU = {
|
||||
deviceName: '',
|
||||
hasAMD: false,
|
||||
hasNVIDIA: false,
|
||||
};
|
||||
} else if (
|
||||
foundDiscreteGPU &&
|
||||
currentGPU &&
|
||||
line.includes('deviceName') &&
|
||||
line.includes('=')
|
||||
) {
|
||||
const parts = line.split('=');
|
||||
if (parts.length >= 2) {
|
||||
const name = parts[1]?.trim();
|
||||
if (name) {
|
||||
currentGPU.deviceName = name;
|
||||
currentGPU.hasAMD =
|
||||
name.toLowerCase().includes('amd') ||
|
||||
name.toLowerCase().includes('radeon');
|
||||
currentGPU.hasNVIDIA =
|
||||
name.toLowerCase().includes('nvidia') ||
|
||||
name.toLowerCase().includes('geforce') ||
|
||||
name.toLowerCase().includes('rtx') ||
|
||||
name.toLowerCase().includes('gtx');
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
foundDiscreteGPU &&
|
||||
currentGPU &&
|
||||
line.includes('driverInfo')
|
||||
) {
|
||||
const mesaMatch = line.match(/Mesa\s+(.+)/);
|
||||
if (mesaMatch) {
|
||||
currentGPU.driverInfo = `Mesa ${mesaMatch[1].trim()}`;
|
||||
}
|
||||
} else if (
|
||||
foundDiscreteGPU &&
|
||||
currentGPU &&
|
||||
line.includes('apiVersion') &&
|
||||
line.includes('=')
|
||||
) {
|
||||
const match = line.match(/=\s*(\d+\.\d+(?:\.\d+)?)/);
|
||||
if (match) {
|
||||
currentGPU.apiVersion = match[1];
|
||||
if (!globalApiVersion) {
|
||||
globalApiVersion = match[1];
|
||||
}
|
||||
}
|
||||
} else if (foundDiscreteGPU && currentGPU && line.includes('GPU')) {
|
||||
if (currentGPU.deviceName) {
|
||||
discreteGPUs.push(currentGPU);
|
||||
}
|
||||
foundDiscreteGPU = false;
|
||||
currentGPU = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundDiscreteGPU && currentGPU && currentGPU.deviceName) {
|
||||
discreteGPUs.push(currentGPU);
|
||||
}
|
||||
}
|
||||
|
||||
vulkanInfoCache = {
|
||||
discreteGPUs,
|
||||
apiVersion: globalApiVersion,
|
||||
};
|
||||
|
||||
return vulkanInfoCache;
|
||||
} catch {
|
||||
vulkanInfoCache = {
|
||||
discreteGPUs: [],
|
||||
};
|
||||
return vulkanInfoCache;
|
||||
}
|
||||
}
|
||||
|
||||
async function detectLinuxGPUViaVulkan() {
|
||||
try {
|
||||
const vulkanInfo = await getVulkanInfo();
|
||||
|
||||
let hasAMD = false;
|
||||
let hasNVIDIA = false;
|
||||
const gpuInfo: string[] = [];
|
||||
|
||||
for (const gpu of vulkanInfo.discreteGPUs) {
|
||||
gpuInfo.push(formatDeviceName(gpu.deviceName));
|
||||
|
||||
if (gpu.hasAMD) {
|
||||
hasAMD = true;
|
||||
}
|
||||
if (gpu.hasNVIDIA) {
|
||||
hasNVIDIA = true;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
hasAMD,
|
||||
hasNVIDIA,
|
||||
gpuInfo: gpuInfo.length > 0 ? gpuInfo : ['No GPU information available'],
|
||||
};
|
||||
} catch {
|
||||
return {
|
||||
hasAMD: false,
|
||||
hasNVIDIA: false,
|
||||
gpuInfo: ['GPU detection failed'],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function detectGPUViaSI() {
|
||||
const graphics = await siGraphics();
|
||||
|
||||
let hasAMD = false;
|
||||
let hasNVIDIA = false;
|
||||
const gpuInfo: string[] = [];
|
||||
|
||||
const discreteControllers = graphics.controllers.filter(
|
||||
(controller) =>
|
||||
controller.busAddress && isDiscreteBusAddress(controller.busAddress)
|
||||
);
|
||||
|
||||
for (const controller of discreteControllers) {
|
||||
if (
|
||||
controller.vendor?.toLowerCase().includes('amd') ||
|
||||
controller.vendor?.toLowerCase().includes('ati')
|
||||
) {
|
||||
hasAMD = true;
|
||||
}
|
||||
|
||||
if (controller.vendor?.toLowerCase().includes('nvidia')) {
|
||||
hasNVIDIA = true;
|
||||
}
|
||||
|
||||
if (controller.model) {
|
||||
gpuInfo.push(controller.model);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
hasAMD,
|
||||
hasNVIDIA,
|
||||
gpuInfo: gpuInfo.length > 0 ? gpuInfo : ['No GPU information available'],
|
||||
};
|
||||
}
|
||||
|
||||
export async function detectGPUCapabilities() {
|
||||
// WARNING: we're not worrying about the users that update their system
|
||||
// during runtime and not restart. Should we be though?
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { readFile, readdir } from 'fs/promises';
|
|||
import { join } from 'path';
|
||||
import { platform } from 'process';
|
||||
import { execa } from 'execa';
|
||||
import { graphics as siGraphics } from 'systeminformation';
|
||||
|
||||
interface CachedGPUInfo {
|
||||
devicePath: string;
|
||||
|
|
@ -241,3 +242,39 @@ export function isDiscreteBusAddress(busAddress?: string) {
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
export async function detectGPUViaSI() {
|
||||
const graphics = await siGraphics();
|
||||
|
||||
let hasAMD = false;
|
||||
let hasNVIDIA = false;
|
||||
const gpuInfo: string[] = [];
|
||||
|
||||
const discreteControllers = graphics.controllers.filter(
|
||||
(controller) =>
|
||||
controller.busAddress && isDiscreteBusAddress(controller.busAddress)
|
||||
);
|
||||
|
||||
for (const controller of discreteControllers) {
|
||||
if (
|
||||
controller.vendor?.toLowerCase().includes('amd') ||
|
||||
controller.vendor?.toLowerCase().includes('ati')
|
||||
) {
|
||||
hasAMD = true;
|
||||
}
|
||||
|
||||
if (controller.vendor?.toLowerCase().includes('nvidia')) {
|
||||
hasNVIDIA = true;
|
||||
}
|
||||
|
||||
if (controller.model) {
|
||||
gpuInfo.push(controller.model);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
hasAMD,
|
||||
hasNVIDIA,
|
||||
gpuInfo: gpuInfo.length > 0 ? gpuInfo : ['No GPU information available'],
|
||||
};
|
||||
}
|
||||
|
|
|
|||
163
src/utils/node/vulkan.ts
Normal file
163
src/utils/node/vulkan.ts
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
import { execa } from 'execa';
|
||||
|
||||
import { formatDeviceName } from '@/utils/format';
|
||||
|
||||
let vulkanInfoCache: {
|
||||
discreteGPUs: {
|
||||
deviceName: string;
|
||||
driverInfo?: string;
|
||||
apiVersion?: string;
|
||||
hasAMD: boolean;
|
||||
hasNVIDIA: boolean;
|
||||
}[];
|
||||
apiVersion?: string;
|
||||
} | null = null;
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
export async function getVulkanInfo() {
|
||||
if (vulkanInfoCache) {
|
||||
return vulkanInfoCache;
|
||||
}
|
||||
|
||||
try {
|
||||
const { stdout } = await execa('vulkaninfo', ['--summary'], {
|
||||
timeout: 3000,
|
||||
reject: false,
|
||||
});
|
||||
|
||||
const discreteGPUs: {
|
||||
deviceName: string;
|
||||
driverInfo?: string;
|
||||
apiVersion?: string;
|
||||
hasAMD: boolean;
|
||||
hasNVIDIA: boolean;
|
||||
}[] = [];
|
||||
let globalApiVersion: string | undefined;
|
||||
|
||||
if (stdout.trim()) {
|
||||
const lines = stdout.split('\n');
|
||||
let foundDiscreteGPU = false;
|
||||
let currentGPU: (typeof discreteGPUs)[0] | null = null;
|
||||
|
||||
for (const line of lines) {
|
||||
if (
|
||||
!globalApiVersion &&
|
||||
line.includes('apiVersion') &&
|
||||
line.includes('=')
|
||||
) {
|
||||
const match = line.match(/=\s*(\d+\.\d+(?:\.\d+)?)/);
|
||||
if (match) {
|
||||
globalApiVersion = match[1];
|
||||
}
|
||||
}
|
||||
|
||||
if (line.includes('PHYSICAL_DEVICE_TYPE_DISCRETE_GPU')) {
|
||||
foundDiscreteGPU = true;
|
||||
currentGPU = {
|
||||
deviceName: '',
|
||||
hasAMD: false,
|
||||
hasNVIDIA: false,
|
||||
};
|
||||
} else if (
|
||||
foundDiscreteGPU &&
|
||||
currentGPU &&
|
||||
line.includes('deviceName') &&
|
||||
line.includes('=')
|
||||
) {
|
||||
const parts = line.split('=');
|
||||
if (parts.length >= 2) {
|
||||
const name = parts[1]?.trim();
|
||||
if (name) {
|
||||
currentGPU.deviceName = name;
|
||||
currentGPU.hasAMD =
|
||||
name.toLowerCase().includes('amd') ||
|
||||
name.toLowerCase().includes('radeon');
|
||||
currentGPU.hasNVIDIA =
|
||||
name.toLowerCase().includes('nvidia') ||
|
||||
name.toLowerCase().includes('geforce') ||
|
||||
name.toLowerCase().includes('rtx') ||
|
||||
name.toLowerCase().includes('gtx');
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
foundDiscreteGPU &&
|
||||
currentGPU &&
|
||||
line.includes('driverInfo')
|
||||
) {
|
||||
const mesaMatch = line.match(/Mesa\s+(.+)/);
|
||||
if (mesaMatch) {
|
||||
currentGPU.driverInfo = `Mesa ${mesaMatch[1].trim()}`;
|
||||
}
|
||||
} else if (
|
||||
foundDiscreteGPU &&
|
||||
currentGPU &&
|
||||
line.includes('apiVersion') &&
|
||||
line.includes('=')
|
||||
) {
|
||||
const match = line.match(/=\s*(\d+\.\d+(?:\.\d+)?)/);
|
||||
if (match) {
|
||||
currentGPU.apiVersion = match[1];
|
||||
if (!globalApiVersion) {
|
||||
globalApiVersion = match[1];
|
||||
}
|
||||
}
|
||||
} else if (foundDiscreteGPU && currentGPU && line.includes('GPU')) {
|
||||
if (currentGPU.deviceName) {
|
||||
discreteGPUs.push(currentGPU);
|
||||
}
|
||||
foundDiscreteGPU = false;
|
||||
currentGPU = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundDiscreteGPU && currentGPU && currentGPU.deviceName) {
|
||||
discreteGPUs.push(currentGPU);
|
||||
}
|
||||
}
|
||||
|
||||
vulkanInfoCache = {
|
||||
discreteGPUs,
|
||||
apiVersion: globalApiVersion,
|
||||
};
|
||||
|
||||
return vulkanInfoCache;
|
||||
} catch {
|
||||
vulkanInfoCache = {
|
||||
discreteGPUs: [],
|
||||
};
|
||||
return vulkanInfoCache;
|
||||
}
|
||||
}
|
||||
|
||||
export async function detectLinuxGPUViaVulkan() {
|
||||
try {
|
||||
const vulkanInfo = await getVulkanInfo();
|
||||
|
||||
let hasAMD = false;
|
||||
let hasNVIDIA = false;
|
||||
const gpuInfo: string[] = [];
|
||||
|
||||
for (const gpu of vulkanInfo.discreteGPUs) {
|
||||
gpuInfo.push(formatDeviceName(gpu.deviceName));
|
||||
|
||||
if (gpu.hasAMD) {
|
||||
hasAMD = true;
|
||||
}
|
||||
if (gpu.hasNVIDIA) {
|
||||
hasNVIDIA = true;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
hasAMD,
|
||||
hasNVIDIA,
|
||||
gpuInfo: gpuInfo.length > 0 ? gpuInfo : ['No GPU information available'],
|
||||
};
|
||||
} catch {
|
||||
return {
|
||||
hasAMD: false,
|
||||
hasNVIDIA: false,
|
||||
gpuInfo: ['GPU detection failed'],
|
||||
};
|
||||
}
|
||||
}
|
||||
10
yarn.lock
10
yarn.lock
|
|
@ -1456,12 +1456,12 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/react@npm:^19.1.13":
|
||||
version: 19.1.13
|
||||
resolution: "@types/react@npm:19.1.13"
|
||||
"@types/react@npm:^19.1.14":
|
||||
version: 19.1.14
|
||||
resolution: "@types/react@npm:19.1.14"
|
||||
dependencies:
|
||||
csstype: "npm:^3.0.2"
|
||||
checksum: 10c0/75e35b54883f5ed07d3b5cb16a4711b6dbb7ec6b74301bcb9bfa697c9d9fff022ec508e1719e7b2c69e2e8b042faac1125be7717b5e5e084f816a2c88e136920
|
||||
checksum: 10c0/0d1629e065413be79c949f011845836e7f515c8f97dbfef057719be2f312b404405d2d064cb28e03363c62c153c100e84268c09dd9aeda488858d45033b520f0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
@ -3866,7 +3866,7 @@ __metadata:
|
|||
"@mantine/core": "npm:8.3.1"
|
||||
"@mantine/hooks": "npm:8.3.1"
|
||||
"@types/node": "npm:^24.5.2"
|
||||
"@types/react": "npm:^19.1.13"
|
||||
"@types/react": "npm:^19.1.14"
|
||||
"@types/react-dom": "npm:^19.1.9"
|
||||
"@types/yauzl": "npm:^2.10.3"
|
||||
"@typescript-eslint/eslint-plugin": "npm:^8.44.1"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue