llm-checker 3.5.4 → 3.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -1
- package/bin/enhanced_cli.js +44 -6
- package/package.json +2 -1
- package/src/ai/multi-objective-selector.js +19 -5
- package/src/hardware/backends/cpu-detector.js +20 -11
- package/src/hardware/detector.js +38 -15
- package/src/hardware/unified-detector.js +140 -26
- package/src/index.js +18 -7
- package/src/runtime/runtime-support.js +14 -1
- package/src/utils/formatter.js +13 -1
- package/src/utils/platform.js +44 -0
- package/src/utils/token-speed-estimator.js +31 -2
package/README.md
CHANGED
|
@@ -40,7 +40,7 @@ Choosing the right LLM for your hardware is complex. With thousands of model var
|
|
|
40
40
|
|:---:|---|---|
|
|
41
41
|
| **200+** | Dynamic Model Pool | Uses full scraped Ollama catalog/variants when available (with curated fallback) |
|
|
42
42
|
| **4D** | Scoring Engine | Quality, Speed, Fit, Context — weighted by use case |
|
|
43
|
-
| **Multi-GPU** | Hardware Detection | Apple Silicon, NVIDIA CUDA, AMD ROCm, Intel Arc, CPU |
|
|
43
|
+
| **Multi-GPU** | Hardware Detection | Apple Silicon, NVIDIA CUDA, AMD ROCm, Intel Arc, CPU, integrated/dedicated inventory visibility |
|
|
44
44
|
| **Calibrated** | Memory Estimation | Bytes-per-parameter formula validated against real Ollama sizes |
|
|
45
45
|
| **Zero** | Native Dependencies | Pure JavaScript — works on any Node.js 16+ system |
|
|
46
46
|
| **Optional** | SQLite Search | Install `sql.js` to unlock `sync`, `search`, and `smart-recommend` |
|
|
@@ -82,6 +82,13 @@ npm install -g llm-checker
|
|
|
82
82
|
npx llm-checker hw-detect
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
+
**Termux (Android):**
|
|
86
|
+
```bash
|
|
87
|
+
pkg update
|
|
88
|
+
pkg install ollama
|
|
89
|
+
npm install -g llm-checker
|
|
90
|
+
```
|
|
91
|
+
|
|
85
92
|
**Requirements:**
|
|
86
93
|
- Node.js 16+ (any version: 16, 18, 20, 22, 24)
|
|
87
94
|
- [Ollama](https://ollama.ai) installed for running models
|
|
@@ -508,6 +515,16 @@ Metal:
|
|
|
508
515
|
Memory Bandwidth: 273GB/s
|
|
509
516
|
```
|
|
510
517
|
|
|
518
|
+
On hybrid or integrated-only systems, `hw-detect` now also surfaces GPU topology explicitly:
|
|
519
|
+
|
|
520
|
+
```
|
|
521
|
+
Dedicated GPUs: NVIDIA GeForce RTX 4060
|
|
522
|
+
Integrated GPUs: Intel Iris Xe Graphics
|
|
523
|
+
Assist path: Integrated/shared-memory GPU detected, runtime remains CPU
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
This makes integrated GPUs visible even when the selected runtime backend is still CPU.
|
|
527
|
+
|
|
511
528
|
### `recommend` — Category Recommendations
|
|
512
529
|
|
|
513
530
|
```bash
|
package/bin/enhanced_cli.js
CHANGED
|
@@ -16,6 +16,7 @@ function getLLMChecker() {
|
|
|
16
16
|
const { getLogger } = require('../src/utils/logger');
|
|
17
17
|
const fs = require('fs');
|
|
18
18
|
const path = require('path');
|
|
19
|
+
const { normalizePlatform, isTermuxEnvironment } = require('../src/utils/platform');
|
|
19
20
|
const {
|
|
20
21
|
SUPPORTED_RUNTIMES,
|
|
21
22
|
normalizeRuntime,
|
|
@@ -513,8 +514,23 @@ if (!program.commands.some((cmd) => cmd.name() === 'help')) {
|
|
|
513
514
|
|
|
514
515
|
// Ollama installation helper
|
|
515
516
|
function getOllamaInstallInstructions() {
|
|
516
|
-
const
|
|
517
|
+
const rawPlatform = os.platform();
|
|
518
|
+
const platform = normalizePlatform(rawPlatform);
|
|
517
519
|
const arch = os.arch();
|
|
520
|
+
|
|
521
|
+
if (isTermuxEnvironment(rawPlatform, process.env)) {
|
|
522
|
+
return {
|
|
523
|
+
name: `Termux (Android${arch ? ` ${arch}` : ''})`,
|
|
524
|
+
downloadUrl: 'https://github.com/termux/termux-packages',
|
|
525
|
+
instructions: [
|
|
526
|
+
'1. Update Termux packages: pkg update',
|
|
527
|
+
'2. Install Ollama from the Termux repository: pkg install ollama',
|
|
528
|
+
'3. Start Ollama in the current shell: ollama serve',
|
|
529
|
+
'4. In a new Termux session, test with: ollama run llama3.2:1b'
|
|
530
|
+
],
|
|
531
|
+
alternativeInstall: 'pkg install ollama'
|
|
532
|
+
};
|
|
533
|
+
}
|
|
518
534
|
|
|
519
535
|
const instructions = {
|
|
520
536
|
'darwin': {
|
|
@@ -546,8 +562,8 @@ function getOllamaInstallInstructions() {
|
|
|
546
562
|
'1. Review official installation options:',
|
|
547
563
|
' https://github.com/ollama/ollama/blob/main/docs/linux.md',
|
|
548
564
|
'2. Prefer a package manager (apt/dnf/pacman) when available',
|
|
549
|
-
'3. Start
|
|
550
|
-
'
|
|
565
|
+
'3. Start Ollama after install:',
|
|
566
|
+
' ollama serve',
|
|
551
567
|
'4. Test with: ollama run llama2:7b'
|
|
552
568
|
],
|
|
553
569
|
alternativeInstall: 'Manual install: https://github.com/ollama/ollama/blob/main/docs/linux.md'
|
|
@@ -856,6 +872,13 @@ function calculateModelCompatibilityScore(model, hardware) {
|
|
|
856
872
|
return Math.max(0, Math.min(100, Math.round(score)));
|
|
857
873
|
}
|
|
858
874
|
|
|
875
|
+
function formatGpuInventoryList(models = []) {
|
|
876
|
+
if (!Array.isArray(models) || models.length === 0) return 'None';
|
|
877
|
+
return models
|
|
878
|
+
.map(({ name, count }) => (count > 1 ? `${count}x ${name}` : name))
|
|
879
|
+
.join(' + ');
|
|
880
|
+
}
|
|
881
|
+
|
|
859
882
|
// Helper function to get hardware tier for display
|
|
860
883
|
function getHardwareTierForDisplay(hardware) {
|
|
861
884
|
const ram = hardware.memory.total;
|
|
@@ -863,9 +886,15 @@ function getHardwareTierForDisplay(hardware) {
|
|
|
863
886
|
const gpuModel = hardware.gpu?.model || '';
|
|
864
887
|
const vramGB = hardware.gpu?.vram || 0;
|
|
865
888
|
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
889
|
+
const integratedGpuInventory = Array.isArray(hardware.summary?.integratedGpuModels)
|
|
890
|
+
? hardware.summary.integratedGpuModels.map(({ name }) => name).join(' ')
|
|
891
|
+
: '';
|
|
892
|
+
const isIntegratedGPU = typeof hardware.summary?.hasIntegratedGPU === 'boolean'
|
|
893
|
+
? hardware.summary.hasIntegratedGPU
|
|
894
|
+
: /iris.*xe|iris.*graphics|uhd.*graphics|vega.*integrated|radeon.*graphics|intel.*integrated|integrated/i.test(`${gpuModel} ${integratedGpuInventory}`);
|
|
895
|
+
const hasDedicatedGPU = typeof hardware.summary?.hasDedicatedGPU === 'boolean'
|
|
896
|
+
? hardware.summary.hasDedicatedGPU
|
|
897
|
+
: (vramGB > 0 && !isIntegratedGPU);
|
|
869
898
|
const isAppleSilicon = process.platform === 'darwin' && (gpuModel.toLowerCase().includes('apple') || gpuModel.toLowerCase().includes('m1') || gpuModel.toLowerCase().includes('m2') || gpuModel.toLowerCase().includes('m3') || gpuModel.toLowerCase().includes('m4'));
|
|
870
899
|
|
|
871
900
|
// Base tier calculation
|
|
@@ -932,6 +961,8 @@ function displaySystemInfo(hardware, analysis) {
|
|
|
932
961
|
const cpuColor = hardware.cpu.cores >= 8 ? chalk.green : hardware.cpu.cores >= 4 ? chalk.yellow : chalk.red;
|
|
933
962
|
const ramColor = hardware.memory.total >= 32 ? chalk.green : hardware.memory.total >= 16 ? chalk.yellow : chalk.red;
|
|
934
963
|
const gpuColor = hardware.gpu.dedicated ? chalk.green : chalk.hex('#FFA500');
|
|
964
|
+
const integratedList = formatGpuInventoryList(hardware.gpu.integratedGpuModels || hardware.summary?.integratedGpuModels);
|
|
965
|
+
const dedicatedList = formatGpuInventoryList(hardware.gpu.dedicatedGpuModels || hardware.summary?.dedicatedGpuModels);
|
|
935
966
|
|
|
936
967
|
const lines = [
|
|
937
968
|
`${chalk.cyan('CPU:')} ${cpuColor(hardware.cpu.brand)} ${chalk.gray(`(${hardware.cpu.cores} cores, ${hardware.cpu.speed}GHz)`)}`,
|
|
@@ -939,6 +970,8 @@ function displaySystemInfo(hardware, analysis) {
|
|
|
939
970
|
`${chalk.cyan('RAM:')} ${ramColor(hardware.memory.total + 'GB')}`,
|
|
940
971
|
`${chalk.cyan('GPU:')} ${gpuColor(hardware.gpu.model || 'Not detected')}`,
|
|
941
972
|
`${chalk.cyan('VRAM:')} ${hardware.gpu.vram === 0 && hardware.gpu.model && hardware.gpu.model.toLowerCase().includes('apple') ? 'Unified Memory' : `${hardware.gpu.vram || 'N/A'}GB`}${hardware.gpu.dedicated ? chalk.green(' (Dedicated)') : chalk.hex('#FFA500')(' (Integrated)')}`,
|
|
973
|
+
`${chalk.cyan('Dedicated GPUs:')} ${chalk.green(dedicatedList)}`,
|
|
974
|
+
`${chalk.cyan('Integrated GPUs:')} ${chalk.hex('#FFA500')(integratedList)}`,
|
|
942
975
|
];
|
|
943
976
|
|
|
944
977
|
const tier = analysis.summary.hardwareTier?.replace('_', ' ').toUpperCase() || 'UNKNOWN';
|
|
@@ -5026,6 +5059,11 @@ program
|
|
|
5026
5059
|
console.log(` Tier: ${chalk.cyan(detector.getHardwareTier().replace('_', ' ').toUpperCase())}`);
|
|
5027
5060
|
console.log(` Max model size: ${chalk.green(detector.getMaxModelSize() + 'GB')}`);
|
|
5028
5061
|
console.log(` Best backend: ${chalk.cyan(hardware.summary.bestBackend)}`);
|
|
5062
|
+
console.log(` Dedicated GPUs: ${chalk.green(formatGpuInventoryList(hardware.summary.dedicatedGpuModels))}`);
|
|
5063
|
+
console.log(` Integrated GPUs: ${chalk.hex('#FFA500')(formatGpuInventoryList(hardware.summary.integratedGpuModels))}`);
|
|
5064
|
+
if (hardware.summary.hasIntegratedGPU && hardware.summary.bestBackend === 'cpu') {
|
|
5065
|
+
console.log(` Assist path: ${chalk.yellow('Integrated/shared-memory GPU detected, runtime remains CPU')}`);
|
|
5066
|
+
}
|
|
5029
5067
|
|
|
5030
5068
|
// CPU
|
|
5031
5069
|
if (hardware.cpu) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "llm-checker",
|
|
3
|
-
"version": "3.5.
|
|
3
|
+
"version": "3.5.6",
|
|
4
4
|
"description": "Intelligent CLI tool with AI-powered model selection that analyzes your hardware and recommends optimal LLM models for your system",
|
|
5
5
|
"bin": {
|
|
6
6
|
"llm-checker": "bin/cli.js",
|
|
@@ -107,6 +107,7 @@
|
|
|
107
107
|
"npm": ">=8.0.0"
|
|
108
108
|
},
|
|
109
109
|
"os": [
|
|
110
|
+
"android",
|
|
110
111
|
"darwin",
|
|
111
112
|
"linux",
|
|
112
113
|
"win32"
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
const { MULTI_OBJECTIVE_WEIGHTS } = require('../models/scoring-config');
|
|
12
|
+
const { normalizePlatform } = require('../utils/platform');
|
|
12
13
|
|
|
13
14
|
class MultiObjectiveSelector {
|
|
14
15
|
constructor() {
|
|
@@ -346,7 +347,14 @@ class MultiObjectiveSelector {
|
|
|
346
347
|
const unified = isAppleSilicon;
|
|
347
348
|
|
|
348
349
|
// Detect PC platform (Windows/Linux) to match main algorithm
|
|
349
|
-
const
|
|
350
|
+
const integratedGpuInventory = Array.isArray(hardware.summary?.integratedGpuModels)
|
|
351
|
+
? hardware.summary.integratedGpuModels.map(({ name }) => name).join(' ')
|
|
352
|
+
: '';
|
|
353
|
+
const hasIntegratedGPU = typeof hardware.summary?.hasIntegratedGPU === 'boolean'
|
|
354
|
+
? hardware.summary.hasIntegratedGPU
|
|
355
|
+
: /iris xe|uhd.*graphics|vega.*integrated|radeon.*graphics/i.test(`${gpuModel} ${integratedGpuInventory}`);
|
|
356
|
+
const platform = normalizePlatform(hardware?.os?.platform || process.platform);
|
|
357
|
+
const isPC = !isAppleSilicon && (platform === 'win32' || platform === 'linux');
|
|
350
358
|
|
|
351
359
|
// 1) Effective memory for model weights (45%) - Apple Silicon & PC optimized
|
|
352
360
|
let effMem;
|
|
@@ -446,7 +454,7 @@ class MultiObjectiveSelector {
|
|
|
446
454
|
tier = bumpTier(tier, +1); // High-end GPU boost
|
|
447
455
|
} else if (!vramGB && !unified) {
|
|
448
456
|
tier = bumpTier(tier, -1); // CPU-only penalty (moderate)
|
|
449
|
-
} else if (
|
|
457
|
+
} else if (hasIntegratedGPU) {
|
|
450
458
|
tier = bumpTier(tier, -1); // iGPU penalty
|
|
451
459
|
} else if (vramGB > 0 && vramGB < 6) {
|
|
452
460
|
tier = bumpTier(tier, -1); // Low VRAM penalty
|
|
@@ -658,7 +666,10 @@ class MultiObjectiveSelector {
|
|
|
658
666
|
const cpuModel = hardware.cpu?.brand || hardware.cpu?.model || '';
|
|
659
667
|
const gpuModel = hardware.gpu?.model || '';
|
|
660
668
|
const cpu = cpuModel.toLowerCase();
|
|
661
|
-
const
|
|
669
|
+
const integratedGpuInventory = Array.isArray(hardware.summary?.integratedGpuModels)
|
|
670
|
+
? hardware.summary.integratedGpuModels.map(({ name }) => name).join(' ')
|
|
671
|
+
: '';
|
|
672
|
+
const gpu = `${gpuModel} ${integratedGpuInventory}`.toLowerCase();
|
|
662
673
|
const cores = hardware.cpu?.physicalCores || hardware.cpu?.cores || 1;
|
|
663
674
|
|
|
664
675
|
let specs = {
|
|
@@ -738,13 +749,16 @@ class MultiObjectiveSelector {
|
|
|
738
749
|
const cores = hardware.cpu?.physicalCores || hardware.cpu?.cores || 1;
|
|
739
750
|
const baseSpeed = hardware.cpu?.speed || 2.0;
|
|
740
751
|
const vramGB = hardware.gpu?.vram || 0;
|
|
752
|
+
const hasIntegratedGPU = typeof hardware.summary?.hasIntegratedGPU === 'boolean'
|
|
753
|
+
? hardware.summary.hasIntegratedGPU
|
|
754
|
+
: false;
|
|
741
755
|
|
|
742
756
|
// Use improved CPU estimation function for more realistic and varying speeds
|
|
743
757
|
const hasAVX512 = cpuModel.toLowerCase().includes('intel') &&
|
|
744
758
|
(cpuModel.includes('13th') || cpuModel.includes('14th') || cpuModel.includes('12th'));
|
|
745
759
|
|
|
746
760
|
// GPU-based calculation (dedicated GPU only)
|
|
747
|
-
if (vramGB > 0 && !
|
|
761
|
+
if (vramGB > 0 && !hasIntegratedGPU) {
|
|
748
762
|
let gpuTPS = 20; // Conservative GPU baseline
|
|
749
763
|
if (gpuModel.toLowerCase().includes('gb10') ||
|
|
750
764
|
gpuModel.toLowerCase().includes('grace blackwell') ||
|
|
@@ -780,7 +794,7 @@ class MultiObjectiveSelector {
|
|
|
780
794
|
threads: cores,
|
|
781
795
|
paramsB: params,
|
|
782
796
|
avx512: hasAVX512,
|
|
783
|
-
isIrisXe: gpuModel.toLowerCase().includes('iris xe')
|
|
797
|
+
isIrisXe: hasIntegratedGPU && gpuModel.toLowerCase().includes('iris xe')
|
|
784
798
|
});
|
|
785
799
|
}
|
|
786
800
|
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
const { execSync } = require('child_process');
|
|
8
8
|
const os = require('os');
|
|
9
9
|
const fs = require('fs');
|
|
10
|
+
const { normalizePlatform } = require('../../utils/platform');
|
|
10
11
|
|
|
11
12
|
class CPUDetector {
|
|
12
13
|
constructor() {
|
|
@@ -87,10 +88,12 @@ class CPUDetector {
|
|
|
87
88
|
* Get physical core count
|
|
88
89
|
*/
|
|
89
90
|
getPhysicalCores() {
|
|
91
|
+
const platform = normalizePlatform();
|
|
92
|
+
|
|
90
93
|
try {
|
|
91
|
-
if (
|
|
94
|
+
if (platform === 'darwin') {
|
|
92
95
|
return parseInt(execSync('sysctl -n hw.physicalcpu', { encoding: 'utf8', timeout: 5000 }).trim());
|
|
93
|
-
} else if (
|
|
96
|
+
} else if (platform === 'linux') {
|
|
94
97
|
const cpuInfo = fs.readFileSync('/proc/cpuinfo', 'utf8');
|
|
95
98
|
const coreIds = new Set();
|
|
96
99
|
const matches = cpuInfo.matchAll(/core id\s*:\s*(\d+)/g);
|
|
@@ -98,7 +101,7 @@ class CPUDetector {
|
|
|
98
101
|
coreIds.add(match[1]);
|
|
99
102
|
}
|
|
100
103
|
return coreIds.size || os.cpus().length;
|
|
101
|
-
} else if (
|
|
104
|
+
} else if (platform === 'win32') {
|
|
102
105
|
const physicalCores = this.getWindowsPhysicalCoreCount();
|
|
103
106
|
return physicalCores || os.cpus().length;
|
|
104
107
|
}
|
|
@@ -112,18 +115,20 @@ class CPUDetector {
|
|
|
112
115
|
* Get maximum CPU frequency
|
|
113
116
|
*/
|
|
114
117
|
getMaxFrequency() {
|
|
118
|
+
const platform = normalizePlatform();
|
|
119
|
+
|
|
115
120
|
try {
|
|
116
|
-
if (
|
|
121
|
+
if (platform === 'darwin') {
|
|
117
122
|
// macOS doesn't expose max frequency easily
|
|
118
123
|
const cpus = os.cpus();
|
|
119
124
|
return cpus.length > 0 ? cpus[0].speed : 0;
|
|
120
|
-
} else if (
|
|
125
|
+
} else if (platform === 'linux') {
|
|
121
126
|
const maxFreq = fs.readFileSync(
|
|
122
127
|
'/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq',
|
|
123
128
|
'utf8'
|
|
124
129
|
);
|
|
125
130
|
return Math.round(parseInt(maxFreq) / 1000); // kHz to MHz
|
|
126
|
-
} else if (
|
|
131
|
+
} else if (platform === 'win32') {
|
|
127
132
|
const maxClock = this.getWindowsMaxClockSpeed();
|
|
128
133
|
return maxClock || (os.cpus()[0]?.speed || 0);
|
|
129
134
|
}
|
|
@@ -206,13 +211,15 @@ class CPUDetector {
|
|
|
206
211
|
l3: 0
|
|
207
212
|
};
|
|
208
213
|
|
|
214
|
+
const platform = normalizePlatform();
|
|
215
|
+
|
|
209
216
|
try {
|
|
210
|
-
if (
|
|
217
|
+
if (platform === 'darwin') {
|
|
211
218
|
cache.l1d = parseInt(execSync('sysctl -n hw.l1dcachesize', { encoding: 'utf8', timeout: 5000 })) / 1024 || 0;
|
|
212
219
|
cache.l1i = parseInt(execSync('sysctl -n hw.l1icachesize', { encoding: 'utf8', timeout: 5000 })) / 1024 || 0;
|
|
213
220
|
cache.l2 = parseInt(execSync('sysctl -n hw.l2cachesize', { encoding: 'utf8', timeout: 5000 })) / 1024 / 1024 || 0;
|
|
214
221
|
cache.l3 = parseInt(execSync('sysctl -n hw.l3cachesize', { encoding: 'utf8', timeout: 5000 })) / 1024 / 1024 || 0;
|
|
215
|
-
} else if (
|
|
222
|
+
} else if (platform === 'linux') {
|
|
216
223
|
// Parse from /sys/devices/system/cpu/cpu0/cache/
|
|
217
224
|
const cachePath = '/sys/devices/system/cpu/cpu0/cache';
|
|
218
225
|
if (fs.existsSync(cachePath)) {
|
|
@@ -268,8 +275,10 @@ class CPUDetector {
|
|
|
268
275
|
bestSimd: 'none'
|
|
269
276
|
};
|
|
270
277
|
|
|
278
|
+
const platform = normalizePlatform();
|
|
279
|
+
|
|
271
280
|
try {
|
|
272
|
-
if (
|
|
281
|
+
if (platform === 'darwin') {
|
|
273
282
|
// For Apple Silicon (ARM64), use ARM features
|
|
274
283
|
if (process.arch === 'arm64') {
|
|
275
284
|
caps.neon = true; // All Apple Silicon has NEON
|
|
@@ -308,7 +317,7 @@ class CPUDetector {
|
|
|
308
317
|
}
|
|
309
318
|
}
|
|
310
319
|
|
|
311
|
-
} else if (
|
|
320
|
+
} else if (platform === 'linux') {
|
|
312
321
|
// Linux - check /proc/cpuinfo
|
|
313
322
|
const cpuInfo = fs.readFileSync('/proc/cpuinfo', 'utf8').toLowerCase();
|
|
314
323
|
const flags = cpuInfo.match(/flags\s*:\s*(.+)/)?.[1] || '';
|
|
@@ -334,7 +343,7 @@ class CPUDetector {
|
|
|
334
343
|
caps.dotprod = flags.includes('asimddp');
|
|
335
344
|
}
|
|
336
345
|
|
|
337
|
-
} else if (
|
|
346
|
+
} else if (platform === 'win32') {
|
|
338
347
|
// Windows - use WMIC or assume based on CPU model
|
|
339
348
|
const cpuName = os.cpus()[0]?.model?.toLowerCase() || '';
|
|
340
349
|
|
package/src/hardware/detector.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const si = require('systeminformation');
|
|
2
2
|
const UnifiedDetector = require('./unified-detector');
|
|
3
|
+
const { normalizePlatform } = require('../utils/platform');
|
|
3
4
|
|
|
4
5
|
class HardwareDetector {
|
|
5
6
|
constructor() {
|
|
@@ -255,24 +256,33 @@ class HardwareDetector {
|
|
|
255
256
|
}
|
|
256
257
|
|
|
257
258
|
const primaryType = unified.primary.type || 'cpu';
|
|
259
|
+
const summary = unified.summary;
|
|
258
260
|
const hasFallbackDedicatedGpu = Boolean(
|
|
259
261
|
primaryType === 'cpu' &&
|
|
260
262
|
unified.systemGpu?.available &&
|
|
261
263
|
Array.isArray(unified.systemGpu.gpus) &&
|
|
262
264
|
unified.systemGpu.gpus.some((gpu) => gpu.type === 'dedicated')
|
|
263
265
|
);
|
|
264
|
-
|
|
265
|
-
if (primaryType === 'cpu' && !hasFallbackDedicatedGpu) {
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
const summary = unified.summary;
|
|
270
266
|
const backendInfo = hasFallbackDedicatedGpu
|
|
271
267
|
? unified.systemGpu
|
|
272
268
|
: (unified.backends?.[primaryType]?.info || {});
|
|
273
269
|
|
|
274
270
|
const backendGPUs = Array.isArray(backendInfo.gpus) ? backendInfo.gpus : [];
|
|
275
271
|
const dedicatedBackendGPUs = backendGPUs.filter((gpu) => gpu?.type !== 'integrated');
|
|
272
|
+
const dedicatedInventoryModels = Array.isArray(summary.dedicatedGpuModels) ? summary.dedicatedGpuModels : [];
|
|
273
|
+
const integratedInventoryModels = Array.isArray(summary.integratedGpuModels) ? summary.integratedGpuModels : [];
|
|
274
|
+
const hasDedicatedGPU = Boolean(
|
|
275
|
+
summary.hasDedicatedGPU ||
|
|
276
|
+
dedicatedInventoryModels.length > 0 ||
|
|
277
|
+
dedicatedBackendGPUs.length > 0
|
|
278
|
+
);
|
|
279
|
+
const hasIntegratedGPU = Boolean(
|
|
280
|
+
summary.hasIntegratedGPU ||
|
|
281
|
+
integratedInventoryModels.length > 0 ||
|
|
282
|
+
backendGPUs.some((gpu) => gpu?.type === 'integrated')
|
|
283
|
+
);
|
|
284
|
+
const primaryDedicatedModel = dedicatedInventoryModels[0]?.name || dedicatedBackendGPUs[0]?.name || null;
|
|
285
|
+
const primaryIntegratedModel = integratedInventoryModels[0]?.name || null;
|
|
276
286
|
|
|
277
287
|
const gpuCount = summary.gpuCount ||
|
|
278
288
|
dedicatedBackendGPUs.length ||
|
|
@@ -287,23 +297,33 @@ class HardwareDetector {
|
|
|
287
297
|
}, 0);
|
|
288
298
|
const totalVRAM = totalVRAMFromUnified || totalVRAMFromFallback || systemInfo.gpu.vram;
|
|
289
299
|
const perGPUVRAM = dedicatedBackendGPUs[0]?.memory?.total ||
|
|
290
|
-
|
|
291
|
-
(gpuCount > 0 && totalVRAM > 0 ? Math.round(totalVRAM / gpuCount) : 0);
|
|
300
|
+
(hasDedicatedGPU && gpuCount > 0 && totalVRAM > 0 ? Math.round(totalVRAM / Math.max(1, summary.dedicatedGpuCount || gpuCount)) : 0);
|
|
292
301
|
|
|
293
|
-
const fallbackModel =
|
|
294
|
-
const modelFromUnified = summary.
|
|
302
|
+
const fallbackModel = primaryDedicatedModel || primaryIntegratedModel || backendGPUs[0]?.name || null;
|
|
303
|
+
const modelFromUnified = summary.gpuModel || fallbackModel || systemInfo.gpu.model;
|
|
295
304
|
const vendor = this.inferVendorFromGPUModel(modelFromUnified, systemInfo.gpu.vendor);
|
|
305
|
+
const isAppleUnified = primaryType === 'metal';
|
|
306
|
+
|
|
307
|
+
systemInfo.summary = {
|
|
308
|
+
...summary
|
|
309
|
+
};
|
|
296
310
|
|
|
297
311
|
systemInfo.gpu = {
|
|
298
312
|
...systemInfo.gpu,
|
|
299
313
|
model: modelFromUnified,
|
|
300
314
|
vendor,
|
|
301
|
-
vram: totalVRAM || systemInfo.gpu.vram,
|
|
302
|
-
vramPerGPU: perGPUVRAM || systemInfo.gpu.vramPerGPU || 0,
|
|
303
|
-
dedicated:
|
|
304
|
-
gpuCount,
|
|
315
|
+
vram: isAppleUnified ? systemInfo.gpu.vram : (hasDedicatedGPU ? (totalVRAM || systemInfo.gpu.vram) : 0),
|
|
316
|
+
vramPerGPU: isAppleUnified ? (systemInfo.gpu.vramPerGPU || 0) : (perGPUVRAM || systemInfo.gpu.vramPerGPU || 0),
|
|
317
|
+
dedicated: hasDedicatedGPU,
|
|
318
|
+
gpuCount: summary.gpuCount || gpuCount,
|
|
305
319
|
isMultiGPU: Boolean(summary.isMultiGPU || gpuCount > 1),
|
|
306
320
|
gpuInventory: summary.gpuInventory || null,
|
|
321
|
+
hasIntegratedGPU,
|
|
322
|
+
hasDedicatedGPU,
|
|
323
|
+
integratedGpuCount: summary.integratedGpuCount || 0,
|
|
324
|
+
dedicatedGpuCount: summary.dedicatedGpuCount || 0,
|
|
325
|
+
integratedGpuModels: integratedInventoryModels,
|
|
326
|
+
dedicatedGpuModels: dedicatedInventoryModels,
|
|
307
327
|
backend: hasFallbackDedicatedGpu ? 'generic' : primaryType,
|
|
308
328
|
driverVersion: backendInfo.driver || systemInfo.gpu.driverVersion
|
|
309
329
|
};
|
|
@@ -324,8 +344,11 @@ class HardwareDetector {
|
|
|
324
344
|
}
|
|
325
345
|
|
|
326
346
|
processOSInfo(osInfo) {
|
|
347
|
+
const rawPlatform = osInfo.platform || process.platform;
|
|
348
|
+
|
|
327
349
|
return {
|
|
328
|
-
platform:
|
|
350
|
+
platform: normalizePlatform(rawPlatform),
|
|
351
|
+
platformRaw: rawPlatform,
|
|
329
352
|
distro: osInfo.distro || 'Unknown',
|
|
330
353
|
release: osInfo.release || 'Unknown',
|
|
331
354
|
codename: osInfo.codename || 'Unknown',
|
|
@@ -11,6 +11,7 @@ const IntelDetector = require('./backends/intel-detector');
|
|
|
11
11
|
const CPUDetector = require('./backends/cpu-detector');
|
|
12
12
|
const si = require('systeminformation');
|
|
13
13
|
const { execSync } = require('child_process');
|
|
14
|
+
const { normalizePlatform } = require('../utils/platform');
|
|
14
15
|
|
|
15
16
|
class UnifiedDetector {
|
|
16
17
|
constructor() {
|
|
@@ -35,6 +36,8 @@ class UnifiedDetector {
|
|
|
35
36
|
return this.cache;
|
|
36
37
|
}
|
|
37
38
|
|
|
39
|
+
const platform = normalizePlatform();
|
|
40
|
+
|
|
38
41
|
const result = {
|
|
39
42
|
backends: {},
|
|
40
43
|
primary: null,
|
|
@@ -64,7 +67,7 @@ class UnifiedDetector {
|
|
|
64
67
|
}
|
|
65
68
|
|
|
66
69
|
// Detect Apple Silicon (macOS ARM only)
|
|
67
|
-
if (
|
|
70
|
+
if (platform === 'darwin' && process.arch === 'arm64') {
|
|
68
71
|
try {
|
|
69
72
|
const metalInfo = this.backends.metal.detect();
|
|
70
73
|
if (metalInfo) {
|
|
@@ -109,7 +112,7 @@ class UnifiedDetector {
|
|
|
109
112
|
}
|
|
110
113
|
|
|
111
114
|
// Detect Intel (Linux only for now)
|
|
112
|
-
if (
|
|
115
|
+
if (platform === 'linux') {
|
|
113
116
|
try {
|
|
114
117
|
if (this.backends.intel.checkAvailability()) {
|
|
115
118
|
const intelInfo = this.backends.intel.detect();
|
|
@@ -125,16 +128,9 @@ class UnifiedDetector {
|
|
|
125
128
|
}
|
|
126
129
|
}
|
|
127
130
|
|
|
128
|
-
//
|
|
129
|
-
//
|
|
130
|
-
|
|
131
|
-
result.backends.cuda?.available ||
|
|
132
|
-
result.backends.rocm?.available ||
|
|
133
|
-
result.backends.metal?.available ||
|
|
134
|
-
result.backends.intel?.available
|
|
135
|
-
);
|
|
136
|
-
|
|
137
|
-
if (!hasAcceleratedBackend && (process.platform === 'win32' || process.platform === 'linux')) {
|
|
131
|
+
// Always collect a generic GPU inventory on Windows/Linux so integrated
|
|
132
|
+
// GPUs remain visible even when a dedicated backend is selected.
|
|
133
|
+
if (platform === 'win32' || platform === 'linux') {
|
|
138
134
|
try {
|
|
139
135
|
const genericGpuInfo = await this.detectSystemGpuFallback();
|
|
140
136
|
if (genericGpuInfo?.available) {
|
|
@@ -229,14 +225,21 @@ class UnifiedDetector {
|
|
|
229
225
|
gpuInventory: null,
|
|
230
226
|
gpuModels: [],
|
|
231
227
|
hasHeterogeneousGPU: false,
|
|
228
|
+
hasIntegratedGPU: false,
|
|
229
|
+
hasDedicatedGPU: false,
|
|
230
|
+
integratedGpuCount: 0,
|
|
231
|
+
dedicatedGpuCount: 0,
|
|
232
|
+
integratedGpuModels: [],
|
|
233
|
+
dedicatedGpuModels: [],
|
|
232
234
|
cpuModel: result.cpu?.brand || 'Unknown',
|
|
233
235
|
systemRAM: require('os').totalmem() / (1024 ** 3)
|
|
234
236
|
};
|
|
235
237
|
|
|
236
238
|
const primary = result.primary;
|
|
239
|
+
const inventorySource = this.getSummaryInventorySource(result);
|
|
237
240
|
|
|
238
241
|
if (primary?.type === 'cuda' && primary.info) {
|
|
239
|
-
const inventory = this.summarizeGPUInventory(
|
|
242
|
+
const inventory = this.summarizeGPUInventory(inventorySource);
|
|
240
243
|
summary.totalVRAM = primary.info.totalVRAM;
|
|
241
244
|
summary.gpuCount = primary.info.gpus.length;
|
|
242
245
|
summary.isMultiGPU = primary.info.isMultiGPU;
|
|
@@ -247,7 +250,7 @@ class UnifiedDetector {
|
|
|
247
250
|
summary.hasHeterogeneousGPU = inventory.isHeterogeneous;
|
|
248
251
|
}
|
|
249
252
|
else if (primary?.type === 'rocm' && primary.info) {
|
|
250
|
-
const inventory = this.summarizeGPUInventory(
|
|
253
|
+
const inventory = this.summarizeGPUInventory(inventorySource);
|
|
251
254
|
summary.totalVRAM = primary.info.totalVRAM;
|
|
252
255
|
summary.gpuCount = primary.info.gpus.length;
|
|
253
256
|
summary.isMultiGPU = primary.info.isMultiGPU;
|
|
@@ -259,15 +262,17 @@ class UnifiedDetector {
|
|
|
259
262
|
}
|
|
260
263
|
else if (primary?.type === 'metal' && primary.info) {
|
|
261
264
|
// Apple Silicon uses unified memory
|
|
265
|
+
const inventory = this.summarizeGPUInventory(inventorySource);
|
|
262
266
|
summary.totalVRAM = primary.info.memory.unified;
|
|
263
267
|
summary.gpuCount = 1;
|
|
264
268
|
summary.speedCoefficient = primary.info.speedCoefficient;
|
|
265
|
-
summary.gpuModel = primary.info.chip || 'Apple Silicon';
|
|
266
|
-
summary.gpuInventory = summary.gpuModel;
|
|
267
|
-
summary.gpuModels =
|
|
269
|
+
summary.gpuModel = inventory.primaryModel || primary.info.chip || 'Apple Silicon';
|
|
270
|
+
summary.gpuInventory = inventory.displayName || summary.gpuModel;
|
|
271
|
+
summary.gpuModels = inventory.models;
|
|
272
|
+
summary.hasHeterogeneousGPU = inventory.isHeterogeneous;
|
|
268
273
|
}
|
|
269
274
|
else if (primary?.type === 'intel' && primary.info) {
|
|
270
|
-
const inventory = this.summarizeGPUInventory(
|
|
275
|
+
const inventory = this.summarizeGPUInventory(inventorySource);
|
|
271
276
|
summary.totalVRAM = primary.info.totalVRAM;
|
|
272
277
|
summary.gpuCount = primary.info.gpus.filter(g => g.type === 'dedicated').length;
|
|
273
278
|
summary.speedCoefficient = primary.info.speedCoefficient;
|
|
@@ -279,10 +284,10 @@ class UnifiedDetector {
|
|
|
279
284
|
else if (result.cpu) {
|
|
280
285
|
summary.speedCoefficient = result.cpu.speedCoefficient;
|
|
281
286
|
|
|
282
|
-
if (
|
|
283
|
-
const inventory = this.summarizeGPUInventory(
|
|
287
|
+
if (inventorySource.length > 0) {
|
|
288
|
+
const inventory = this.summarizeGPUInventory(inventorySource);
|
|
284
289
|
summary.totalVRAM = result.systemGpu.totalVRAM || 0;
|
|
285
|
-
summary.gpuCount =
|
|
290
|
+
summary.gpuCount = inventory.dedicatedCount > 0 ? inventory.dedicatedCount : inventory.gpuCount;
|
|
286
291
|
summary.isMultiGPU = Boolean(result.systemGpu.isMultiGPU);
|
|
287
292
|
summary.gpuModel = inventory.primaryModel || null;
|
|
288
293
|
summary.gpuInventory = inventory.displayName || summary.gpuModel;
|
|
@@ -291,6 +296,24 @@ class UnifiedDetector {
|
|
|
291
296
|
}
|
|
292
297
|
}
|
|
293
298
|
|
|
299
|
+
const topology = this.summarizeGPUInventory(inventorySource);
|
|
300
|
+
summary.hasIntegratedGPU = topology.hasIntegratedGPU;
|
|
301
|
+
summary.hasDedicatedGPU = topology.hasDedicatedGPU;
|
|
302
|
+
summary.integratedGpuCount = topology.integratedCount;
|
|
303
|
+
summary.dedicatedGpuCount = topology.dedicatedCount;
|
|
304
|
+
summary.integratedGpuModels = topology.integratedModels;
|
|
305
|
+
summary.dedicatedGpuModels = topology.dedicatedModels;
|
|
306
|
+
if (!summary.gpuModel) {
|
|
307
|
+
summary.gpuModel = topology.primaryModel || null;
|
|
308
|
+
}
|
|
309
|
+
if (!summary.gpuInventory) {
|
|
310
|
+
summary.gpuInventory = topology.displayName || summary.gpuModel;
|
|
311
|
+
}
|
|
312
|
+
if (!summary.gpuModels.length) {
|
|
313
|
+
summary.gpuModels = topology.models;
|
|
314
|
+
}
|
|
315
|
+
summary.hasHeterogeneousGPU = summary.hasHeterogeneousGPU || topology.isHeterogeneous;
|
|
316
|
+
|
|
294
317
|
// Effective memory for LLM loading
|
|
295
318
|
// For GPU: use VRAM; for CPU/Metal: use system RAM
|
|
296
319
|
if (summary.totalVRAM > 0 && ['cuda', 'rocm', 'intel'].includes(primary?.type)) {
|
|
@@ -304,26 +327,110 @@ class UnifiedDetector {
|
|
|
304
327
|
}
|
|
305
328
|
|
|
306
329
|
summarizeGPUInventory(gpus = []) {
|
|
330
|
+
const normalized = this.normalizeGpuInventory(gpus);
|
|
307
331
|
const counts = new Map();
|
|
332
|
+
const integratedCounts = new Map();
|
|
333
|
+
const dedicatedCounts = new Map();
|
|
308
334
|
|
|
309
|
-
for (const gpu of
|
|
335
|
+
for (const gpu of normalized) {
|
|
310
336
|
const name = (gpu?.name || 'Unknown GPU').replace(/\s+/g, ' ').trim();
|
|
311
337
|
counts.set(name, (counts.get(name) || 0) + 1);
|
|
338
|
+
if (gpu.type === 'integrated') {
|
|
339
|
+
integratedCounts.set(name, (integratedCounts.get(name) || 0) + 1);
|
|
340
|
+
} else {
|
|
341
|
+
dedicatedCounts.set(name, (dedicatedCounts.get(name) || 0) + 1);
|
|
342
|
+
}
|
|
312
343
|
}
|
|
313
344
|
|
|
314
345
|
const models = Array.from(counts.entries()).map(([name, count]) => ({ name, count }));
|
|
346
|
+
const integratedModels = Array.from(integratedCounts.entries()).map(([name, count]) => ({ name, count }));
|
|
347
|
+
const dedicatedModels = Array.from(dedicatedCounts.entries()).map(([name, count]) => ({ name, count }));
|
|
315
348
|
const displayName = models
|
|
316
349
|
.map(({ name, count }) => (count > 1 ? `${count}x ${name}` : name))
|
|
317
350
|
.join(' + ');
|
|
318
351
|
|
|
319
352
|
return {
|
|
320
|
-
primaryModel: models[0]?.name || null,
|
|
353
|
+
primaryModel: dedicatedModels[0]?.name || integratedModels[0]?.name || models[0]?.name || null,
|
|
321
354
|
displayName: displayName || null,
|
|
322
355
|
models,
|
|
356
|
+
integratedModels,
|
|
357
|
+
dedicatedModels,
|
|
358
|
+
integratedCount: normalized.filter((gpu) => gpu.type === 'integrated').length,
|
|
359
|
+
dedicatedCount: normalized.filter((gpu) => gpu.type === 'dedicated').length,
|
|
360
|
+
hasIntegratedGPU: normalized.some((gpu) => gpu.type === 'integrated'),
|
|
361
|
+
hasDedicatedGPU: normalized.some((gpu) => gpu.type === 'dedicated'),
|
|
362
|
+
gpuCount: normalized.length,
|
|
323
363
|
isHeterogeneous: models.length > 1
|
|
324
364
|
};
|
|
325
365
|
}
|
|
326
366
|
|
|
367
|
+
getSummaryInventorySource(result) {
|
|
368
|
+
const primaryInventory = this.getPrimaryInventoryGpus(result.primary);
|
|
369
|
+
const systemInventory = Array.isArray(result.systemGpu?.gpus) ? result.systemGpu.gpus : [];
|
|
370
|
+
return this.mergeGpuInventories(primaryInventory, systemInventory);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
getPrimaryInventoryGpus(primary) {
|
|
374
|
+
if (!primary?.info) return [];
|
|
375
|
+
|
|
376
|
+
if (Array.isArray(primary.info.gpus) && primary.info.gpus.length > 0) {
|
|
377
|
+
return primary.info.gpus;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (primary.type === 'metal') {
|
|
381
|
+
return [{
|
|
382
|
+
name: primary.info.chip || 'Apple Silicon',
|
|
383
|
+
type: 'integrated',
|
|
384
|
+
memory: { total: primary.info.memory?.unified || 0 }
|
|
385
|
+
}];
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return [];
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
normalizeGpuInventory(gpus = []) {
|
|
392
|
+
return gpus
|
|
393
|
+
.map((gpu) => {
|
|
394
|
+
const name = String(gpu?.name || gpu?.model || '').replace(/\s+/g, ' ').trim();
|
|
395
|
+
if (!name) return null;
|
|
396
|
+
|
|
397
|
+
let type = gpu?.type;
|
|
398
|
+
if (type !== 'integrated' && type !== 'dedicated') {
|
|
399
|
+
type = this.isIntegratedGPUModel(name) ? 'integrated' : 'dedicated';
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const totalMemory = Number(gpu?.memory?.total || gpu?.memoryTotal || gpu?.memory || 0);
|
|
403
|
+
|
|
404
|
+
return {
|
|
405
|
+
name,
|
|
406
|
+
type,
|
|
407
|
+
memory: {
|
|
408
|
+
total: Number.isFinite(totalMemory) ? totalMemory : 0
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
})
|
|
412
|
+
.filter(Boolean);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
mergeGpuInventories(...gpuLists) {
|
|
416
|
+
const normalizedLists = gpuLists.map((list) => this.normalizeGpuInventory(list));
|
|
417
|
+
const primaryIndex = normalizedLists.findIndex((list) => list.length > 0);
|
|
418
|
+
const merged = [];
|
|
419
|
+
const seen = new Set();
|
|
420
|
+
|
|
421
|
+
normalizedLists.forEach((list, listIndex) => {
|
|
422
|
+
for (const gpu of list) {
|
|
423
|
+
const key = `${this.getGpuMatchKey(gpu.name)}|${gpu.type}`;
|
|
424
|
+
if (!key) continue;
|
|
425
|
+
if (listIndex !== primaryIndex && seen.has(key)) continue;
|
|
426
|
+
merged.push(gpu);
|
|
427
|
+
seen.add(key);
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
return merged;
|
|
432
|
+
}
|
|
433
|
+
|
|
327
434
|
async detectSystemGpuFallback() {
|
|
328
435
|
const graphics = await si.graphics();
|
|
329
436
|
const controllers = Array.isArray(graphics?.controllers) ? graphics.controllers : [];
|
|
@@ -364,7 +471,9 @@ class UnifiedDetector {
|
|
|
364
471
|
})
|
|
365
472
|
.filter(Boolean);
|
|
366
473
|
|
|
367
|
-
|
|
474
|
+
const platform = normalizePlatform();
|
|
475
|
+
|
|
476
|
+
if (platform === 'linux') {
|
|
368
477
|
const lspciControllers = this.detectLinuxLspciGpus();
|
|
369
478
|
const knownKeys = new Set(
|
|
370
479
|
normalized.map((gpu) => this.getGpuMatchKey(gpu.name)).filter(Boolean)
|
|
@@ -396,7 +505,7 @@ class UnifiedDetector {
|
|
|
396
505
|
|
|
397
506
|
return {
|
|
398
507
|
available: true,
|
|
399
|
-
source:
|
|
508
|
+
source: normalized.some((gpu) => gpu.source === 'lspci') ? 'systeminformation+lspci' : 'systeminformation',
|
|
400
509
|
gpus: normalized,
|
|
401
510
|
totalVRAM,
|
|
402
511
|
isMultiGPU: dedicated.length > 1,
|
|
@@ -457,7 +566,8 @@ class UnifiedDetector {
|
|
|
457
566
|
name,
|
|
458
567
|
vendor: isNvidia ? 'NVIDIA' : (isAMD ? 'AMD' : 'Intel'),
|
|
459
568
|
type: isIntegrated ? 'integrated' : 'dedicated',
|
|
460
|
-
memory: { total: vram }
|
|
569
|
+
memory: { total: vram },
|
|
570
|
+
source: 'lspci'
|
|
461
571
|
});
|
|
462
572
|
}
|
|
463
573
|
|
|
@@ -709,6 +819,10 @@ class UnifiedDetector {
|
|
|
709
819
|
return `${gpuDesc} (${summary.totalVRAM}GB) + ${summary.cpuModel}`;
|
|
710
820
|
}
|
|
711
821
|
else {
|
|
822
|
+
if (summary.gpuModel && summary.hasIntegratedGPU && !summary.hasDedicatedGPU) {
|
|
823
|
+
const gpuDesc = summary.gpuInventory || summary.gpuModel;
|
|
824
|
+
return `${gpuDesc} (integrated/shared memory, CPU backend) + ${summary.cpuModel}`;
|
|
825
|
+
}
|
|
712
826
|
if (summary.gpuModel && summary.gpuCount > 0) {
|
|
713
827
|
const gpuDesc = summary.gpuInventory || summary.gpuModel;
|
|
714
828
|
return `${gpuDesc} (${summary.totalVRAM}GB VRAM detected, CPU backend) + ${summary.cpuModel}`;
|
package/src/index.js
CHANGED
|
@@ -18,6 +18,7 @@ const {
|
|
|
18
18
|
attachModelProvenance,
|
|
19
19
|
attachProvenanceToCollection
|
|
20
20
|
} = require('./provenance/model-provenance');
|
|
21
|
+
const { normalizePlatform } = require('./utils/platform');
|
|
21
22
|
|
|
22
23
|
class LLMChecker {
|
|
23
24
|
constructor(options = {}) {
|
|
@@ -72,7 +73,7 @@ class LLMChecker {
|
|
|
72
73
|
this.logger.info('Hardware detected', { hardware });
|
|
73
74
|
|
|
74
75
|
// Detect platform and route to appropriate logic (use hardware OS for simulation support)
|
|
75
|
-
const detectedPlatform = hardware.os?.platform || process.platform;
|
|
76
|
+
const detectedPlatform = normalizePlatform(hardware.os?.platform || process.platform);
|
|
76
77
|
|
|
77
78
|
// Report hardware detection progress before platform-specific analysis
|
|
78
79
|
if (this.progress) {
|
|
@@ -1370,7 +1371,8 @@ class LLMChecker {
|
|
|
1370
1371
|
const unified = isAppleSilicon;
|
|
1371
1372
|
|
|
1372
1373
|
// Detect PC platform (Windows/Linux)
|
|
1373
|
-
const
|
|
1374
|
+
const normalizedPlatform = normalizePlatform(hardware.os?.platform || process.platform);
|
|
1375
|
+
const isPC = !isAppleSilicon && (normalizedPlatform === 'win32' || normalizedPlatform === 'linux');
|
|
1374
1376
|
const hasAVX512 = cpuModel.toLowerCase().includes('intel') &&
|
|
1375
1377
|
(cpuModel.includes('12th') || cpuModel.includes('13th') || cpuModel.includes('14th'));
|
|
1376
1378
|
const hasAVX2 = cpuModel.toLowerCase().includes('intel') ||
|
|
@@ -1434,7 +1436,7 @@ class LLMChecker {
|
|
|
1434
1436
|
compute = clamp(tflopsFP16 / 80); // GPU: normalizado contra 80 TFLOPs (más realista)
|
|
1435
1437
|
|
|
1436
1438
|
// Cap iGPU compute
|
|
1437
|
-
if (/iris xe|uhd|vega.*integrated|radeon.*graphics/i.test(gpuModel)) {
|
|
1439
|
+
if (Boolean(hardware.summary?.hasIntegratedGPU) || /iris xe|uhd|vega.*integrated|radeon.*graphics/i.test(gpuModel)) {
|
|
1438
1440
|
compute = Math.min(compute, 0.15);
|
|
1439
1441
|
}
|
|
1440
1442
|
} else {
|
|
@@ -1467,9 +1469,15 @@ class LLMChecker {
|
|
|
1467
1469
|
score >= 35 ? 'medium' : // 35-54 for mid-range systems
|
|
1468
1470
|
score >= 20 ? 'low' : 'ultra_low'; // 20-34 for budget systems
|
|
1469
1471
|
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1472
|
+
const integratedGpuInventory = Array.isArray(hardware.summary?.integratedGpuModels)
|
|
1473
|
+
? hardware.summary.integratedGpuModels.map(({ name }) => name).join(' ')
|
|
1474
|
+
: '';
|
|
1475
|
+
const hasIntegratedGPU = typeof hardware.summary?.hasIntegratedGPU === 'boolean'
|
|
1476
|
+
? hardware.summary.hasIntegratedGPU
|
|
1477
|
+
: /iris.*xe|iris.*graphics|uhd.*graphics|vega.*integrated|radeon.*graphics|intel.*integrated|integrated/i.test(`${gpuModel} ${integratedGpuInventory}`);
|
|
1478
|
+
const hasDedicatedGPU = typeof hardware.summary?.hasDedicatedGPU === 'boolean'
|
|
1479
|
+
? (!unified && hardware.summary.hasDedicatedGPU)
|
|
1480
|
+
: (vramGB > 0 && !hasIntegratedGPU && !unified);
|
|
1473
1481
|
|
|
1474
1482
|
// Debug logging for tier calculation
|
|
1475
1483
|
if (process.env.DEBUG_TIER) {
|
|
@@ -1747,7 +1755,10 @@ class LLMChecker {
|
|
|
1747
1755
|
const cpuModel = hardware.cpu?.brand || hardware.cpu?.model || '';
|
|
1748
1756
|
const gpuModel = hardware.gpu?.model || '';
|
|
1749
1757
|
const cpu = cpuModel.toLowerCase();
|
|
1750
|
-
const
|
|
1758
|
+
const integratedGpuInventory = Array.isArray(hardware.summary?.integratedGpuModels)
|
|
1759
|
+
? hardware.summary.integratedGpuModels.map(({ name }) => name).join(' ')
|
|
1760
|
+
: '';
|
|
1761
|
+
const gpu = `${gpuModel} ${integratedGpuInventory}`.toLowerCase();
|
|
1751
1762
|
const cores = hardware.cpu?.physicalCores || hardware.cpu?.cores || 1;
|
|
1752
1763
|
|
|
1753
1764
|
let specs = {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const SUPPORTED_RUNTIMES = ['ollama', 'vllm', 'mlx'];
|
|
2
|
+
const { normalizePlatform, isTermuxEnvironment } = require('../utils/platform');
|
|
2
3
|
|
|
3
4
|
function normalizeRuntime(runtime = 'ollama') {
|
|
4
5
|
const normalized = String(runtime || 'ollama').trim().toLowerCase();
|
|
@@ -114,7 +115,19 @@ function getRuntimeInstallCommand(runtime = 'ollama') {
|
|
|
114
115
|
return 'pip install -U mlx-lm';
|
|
115
116
|
}
|
|
116
117
|
|
|
117
|
-
|
|
118
|
+
if (isTermuxEnvironment()) {
|
|
119
|
+
return 'pkg install ollama';
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const platform = normalizePlatform();
|
|
123
|
+
if (platform === 'darwin') {
|
|
124
|
+
return 'brew install ollama';
|
|
125
|
+
}
|
|
126
|
+
if (platform === 'win32') {
|
|
127
|
+
return 'winget install Ollama.Ollama';
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return 'curl -fsSL https://ollama.com/install.sh | sh';
|
|
118
131
|
}
|
|
119
132
|
|
|
120
133
|
function getRuntimePullCommand(model = {}, runtime = 'ollama') {
|
package/src/utils/formatter.js
CHANGED
|
@@ -22,6 +22,18 @@ class OutputFormatter {
|
|
|
22
22
|
? 'Unified Memory'
|
|
23
23
|
: `${hardware.gpu.vram || 'N/A'}GB`;
|
|
24
24
|
lines.push(this.info('VRAM', `${vramDisplay}${hardware.gpu.dedicated ? ' (Dedicated)' : ' (Integrated)'}`));
|
|
25
|
+
if (Array.isArray(hardware.gpu.dedicatedGpuModels) && hardware.gpu.dedicatedGpuModels.length > 0) {
|
|
26
|
+
const dedicated = hardware.gpu.dedicatedGpuModels
|
|
27
|
+
.map(({ name, count }) => (count > 1 ? `${count}x ${name}` : name))
|
|
28
|
+
.join(' + ');
|
|
29
|
+
lines.push(this.info('Dedicated GPUs', dedicated));
|
|
30
|
+
}
|
|
31
|
+
if (Array.isArray(hardware.gpu.integratedGpuModels) && hardware.gpu.integratedGpuModels.length > 0) {
|
|
32
|
+
const integrated = hardware.gpu.integratedGpuModels
|
|
33
|
+
.map(({ name, count }) => (count > 1 ? `${count}x ${name}` : name))
|
|
34
|
+
.join(' + ');
|
|
35
|
+
lines.push(this.info('Integrated GPUs', integrated));
|
|
36
|
+
}
|
|
25
37
|
lines.push(this.info('OS', `${hardware.os.distro} ${hardware.os.release} (${hardware.os.arch})`));
|
|
26
38
|
|
|
27
39
|
return lines.join('\n');
|
|
@@ -312,4 +324,4 @@ class OutputFormatter {
|
|
|
312
324
|
}
|
|
313
325
|
}
|
|
314
326
|
|
|
315
|
-
module.exports = OutputFormatter;
|
|
327
|
+
module.exports = OutputFormatter;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
function normalizePlatform(platform = process.platform) {
|
|
2
|
+
const normalized = String(platform || process.platform).trim().toLowerCase();
|
|
3
|
+
|
|
4
|
+
if (normalized === 'android') {
|
|
5
|
+
return 'linux';
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
if (normalized === 'macos') {
|
|
9
|
+
return 'darwin';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return normalized;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function isLinuxPlatform(platform = process.platform) {
|
|
16
|
+
return normalizePlatform(platform) === 'linux';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function isWindowsPlatform(platform = process.platform) {
|
|
20
|
+
return normalizePlatform(platform) === 'win32';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function isDarwinPlatform(platform = process.platform) {
|
|
24
|
+
return normalizePlatform(platform) === 'darwin';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function isTermuxEnvironment(platform = process.platform, env = process.env) {
|
|
28
|
+
if (String(platform || process.platform).trim().toLowerCase() === 'android') {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const prefix = String(env?.PREFIX || '');
|
|
33
|
+
const termuxVersion = String(env?.TERMUX_VERSION || '');
|
|
34
|
+
|
|
35
|
+
return prefix.includes('com.termux') || termuxVersion.length > 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = {
|
|
39
|
+
normalizePlatform,
|
|
40
|
+
isLinuxPlatform,
|
|
41
|
+
isWindowsPlatform,
|
|
42
|
+
isDarwinPlatform,
|
|
43
|
+
isTermuxEnvironment
|
|
44
|
+
};
|
|
@@ -31,6 +31,35 @@ function isIntegratedGPU(gpuModel = '') {
|
|
|
31
31
|
return /iris.*xe|iris.*graphics|uhd.*graphics|vega.*integrated|radeon.*graphics|intel.*integrated|integrated/i.test(gpuModel);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
function getIntegratedGpuNames(hardware = {}) {
|
|
35
|
+
if (!Array.isArray(hardware.summary?.integratedGpuModels)) return '';
|
|
36
|
+
return hardware.summary.integratedGpuModels
|
|
37
|
+
.map((model) => model?.name)
|
|
38
|
+
.filter(Boolean)
|
|
39
|
+
.join(' ');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function detectIntegratedGpu(hardware = {}, gpuModel = '') {
|
|
43
|
+
if (typeof hardware.summary?.hasIntegratedGPU === 'boolean') {
|
|
44
|
+
return hardware.summary.hasIntegratedGPU;
|
|
45
|
+
}
|
|
46
|
+
if (typeof hardware.gpu?.hasIntegratedGPU === 'boolean') {
|
|
47
|
+
return hardware.gpu.hasIntegratedGPU;
|
|
48
|
+
}
|
|
49
|
+
return isIntegratedGPU(`${gpuModel} ${getIntegratedGpuNames(hardware)}`.trim());
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function detectDedicatedGpu(hardware = {}, integrated = false, appleSilicon = false, vramGB = 0) {
|
|
53
|
+
if (appleSilicon) return false;
|
|
54
|
+
if (typeof hardware.summary?.hasDedicatedGPU === 'boolean') {
|
|
55
|
+
return hardware.summary.hasDedicatedGPU && vramGB > 0;
|
|
56
|
+
}
|
|
57
|
+
if (typeof hardware.gpu?.hasDedicatedGPU === 'boolean') {
|
|
58
|
+
return hardware.gpu.hasDedicatedGPU && vramGB > 0;
|
|
59
|
+
}
|
|
60
|
+
return vramGB > 0 && !integrated;
|
|
61
|
+
}
|
|
62
|
+
|
|
34
63
|
function getAppleSiliconBaseline(cpuModel, gpuModel) {
|
|
35
64
|
const signal = `${cpuModel} ${gpuModel}`.toLowerCase();
|
|
36
65
|
const profiles = [
|
|
@@ -162,8 +191,8 @@ function estimateTokenSpeedFromHardware(hardware = {}, options = {}) {
|
|
|
162
191
|
);
|
|
163
192
|
|
|
164
193
|
const appleSilicon = detectAppleSilicon(architecture, cpuModel, gpuModel);
|
|
165
|
-
const integrated =
|
|
166
|
-
const dedicatedGPU =
|
|
194
|
+
const integrated = detectIntegratedGpu(hardware, gpuModel);
|
|
195
|
+
const dedicatedGPU = detectDedicatedGpu(hardware, integrated, appleSilicon, vramGB);
|
|
167
196
|
|
|
168
197
|
let baselineTPS7B;
|
|
169
198
|
let backend;
|