llm-checker 3.5.4 → 3.5.7
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/bin/mcp-server.mjs +1 -1
- package/package.json +2 -1
- package/src/ai/multi-objective-selector.js +27 -7
- package/src/hardware/backends/cpu-detector.js +59 -20
- package/src/hardware/backends/cuda-detector.js +13 -8
- package/src/hardware/detector.js +140 -23
- package/src/hardware/unified-detector.js +192 -27
- package/src/index.js +21 -11
- package/src/models/deterministic-selector.js +145 -31
- 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/bin/mcp-server.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "llm-checker",
|
|
3
|
-
"version": "3.5.
|
|
3
|
+
"version": "3.5.7",
|
|
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,12 +347,22 @@ 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 hasDedicatedGPU = typeof hardware.summary?.hasDedicatedGPU === 'boolean'
|
|
357
|
+
? hardware.summary.hasDedicatedGPU
|
|
358
|
+
: Boolean(hardware.gpu?.dedicated || (vramGB > 0 && !hasIntegratedGPU));
|
|
359
|
+
const platform = normalizePlatform(hardware?.os?.platform || process.platform);
|
|
360
|
+
const isPC = !isAppleSilicon && (platform === 'win32' || platform === 'linux');
|
|
350
361
|
|
|
351
362
|
// 1) Effective memory for model weights (45%) - Apple Silicon & PC optimized
|
|
352
363
|
let effMem;
|
|
353
364
|
|
|
354
|
-
if (vramGB > 0 && !unified) {
|
|
365
|
+
if (hasDedicatedGPU && vramGB > 0 && !unified) {
|
|
355
366
|
// Dedicated GPU path (Windows/Linux with discrete GPU)
|
|
356
367
|
if (isPC) {
|
|
357
368
|
// PC-specific GPU memory calculation with offload support
|
|
@@ -446,9 +457,9 @@ class MultiObjectiveSelector {
|
|
|
446
457
|
tier = bumpTier(tier, +1); // High-end GPU boost
|
|
447
458
|
} else if (!vramGB && !unified) {
|
|
448
459
|
tier = bumpTier(tier, -1); // CPU-only penalty (moderate)
|
|
449
|
-
} else if (
|
|
460
|
+
} else if (hasIntegratedGPU && !hasDedicatedGPU) {
|
|
450
461
|
tier = bumpTier(tier, -1); // iGPU penalty
|
|
451
|
-
} else if (vramGB > 0 && vramGB < 6) {
|
|
462
|
+
} else if (hasDedicatedGPU && vramGB > 0 && vramGB < 6) {
|
|
452
463
|
tier = bumpTier(tier, -1); // Low VRAM penalty
|
|
453
464
|
}
|
|
454
465
|
|
|
@@ -658,7 +669,10 @@ class MultiObjectiveSelector {
|
|
|
658
669
|
const cpuModel = hardware.cpu?.brand || hardware.cpu?.model || '';
|
|
659
670
|
const gpuModel = hardware.gpu?.model || '';
|
|
660
671
|
const cpu = cpuModel.toLowerCase();
|
|
661
|
-
const
|
|
672
|
+
const integratedGpuInventory = Array.isArray(hardware.summary?.integratedGpuModels)
|
|
673
|
+
? hardware.summary.integratedGpuModels.map(({ name }) => name).join(' ')
|
|
674
|
+
: '';
|
|
675
|
+
const gpu = `${gpuModel} ${integratedGpuInventory}`.toLowerCase();
|
|
662
676
|
const cores = hardware.cpu?.physicalCores || hardware.cpu?.cores || 1;
|
|
663
677
|
|
|
664
678
|
let specs = {
|
|
@@ -738,13 +752,19 @@ class MultiObjectiveSelector {
|
|
|
738
752
|
const cores = hardware.cpu?.physicalCores || hardware.cpu?.cores || 1;
|
|
739
753
|
const baseSpeed = hardware.cpu?.speed || 2.0;
|
|
740
754
|
const vramGB = hardware.gpu?.vram || 0;
|
|
755
|
+
const hasIntegratedGPU = typeof hardware.summary?.hasIntegratedGPU === 'boolean'
|
|
756
|
+
? hardware.summary.hasIntegratedGPU
|
|
757
|
+
: false;
|
|
758
|
+
const hasDedicatedGPU = typeof hardware.summary?.hasDedicatedGPU === 'boolean'
|
|
759
|
+
? hardware.summary.hasDedicatedGPU
|
|
760
|
+
: Boolean(hardware.gpu?.dedicated || (vramGB > 0 && !hasIntegratedGPU));
|
|
741
761
|
|
|
742
762
|
// Use improved CPU estimation function for more realistic and varying speeds
|
|
743
763
|
const hasAVX512 = cpuModel.toLowerCase().includes('intel') &&
|
|
744
764
|
(cpuModel.includes('13th') || cpuModel.includes('14th') || cpuModel.includes('12th'));
|
|
745
765
|
|
|
746
766
|
// GPU-based calculation (dedicated GPU only)
|
|
747
|
-
if (vramGB > 0
|
|
767
|
+
if (hasDedicatedGPU && vramGB > 0) {
|
|
748
768
|
let gpuTPS = 20; // Conservative GPU baseline
|
|
749
769
|
if (gpuModel.toLowerCase().includes('gb10') ||
|
|
750
770
|
gpuModel.toLowerCase().includes('grace blackwell') ||
|
|
@@ -780,7 +800,7 @@ class MultiObjectiveSelector {
|
|
|
780
800
|
threads: cores,
|
|
781
801
|
paramsB: params,
|
|
782
802
|
avx512: hasAVX512,
|
|
783
|
-
isIrisXe: gpuModel.toLowerCase().includes('iris xe')
|
|
803
|
+
isIrisXe: hasIntegratedGPU && gpuModel.toLowerCase().includes('iris xe')
|
|
784
804
|
});
|
|
785
805
|
}
|
|
786
806
|
}
|
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
* Focuses on AVX2, AVX512, AMX, and other SIMD extensions
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
const
|
|
7
|
+
const childProcess = 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 (
|
|
92
|
-
return parseInt(execSync('sysctl -n hw.physicalcpu', { encoding: 'utf8', timeout: 5000 }).trim());
|
|
93
|
-
} else if (
|
|
94
|
+
if (platform === 'darwin') {
|
|
95
|
+
return parseInt(childProcess.execSync('sysctl -n hw.physicalcpu', { encoding: 'utf8', timeout: 5000 }).trim());
|
|
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
|
}
|
|
@@ -137,7 +142,37 @@ class CPUDetector {
|
|
|
137
142
|
* Execute shell command with consistent options.
|
|
138
143
|
*/
|
|
139
144
|
runCommand(command) {
|
|
140
|
-
|
|
145
|
+
const baseOptions = {
|
|
146
|
+
encoding: 'utf8',
|
|
147
|
+
timeout: 5000
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
if (normalizePlatform() === 'win32') {
|
|
151
|
+
const result = childProcess.spawnSync(command, {
|
|
152
|
+
...baseOptions,
|
|
153
|
+
shell: true,
|
|
154
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
155
|
+
windowsHide: true
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
if (result.error) {
|
|
159
|
+
throw result.error;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (result.status !== 0) {
|
|
163
|
+
const stderr = String(result.stderr || '').trim();
|
|
164
|
+
const stdout = String(result.stdout || '').trim();
|
|
165
|
+
const error = new Error(stderr || stdout || `Command failed: ${command}`);
|
|
166
|
+
error.status = result.status;
|
|
167
|
+
error.stdout = result.stdout;
|
|
168
|
+
error.stderr = result.stderr;
|
|
169
|
+
throw error;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return result.stdout;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return childProcess.execSync(command, baseOptions);
|
|
141
176
|
}
|
|
142
177
|
|
|
143
178
|
/**
|
|
@@ -206,13 +241,15 @@ class CPUDetector {
|
|
|
206
241
|
l3: 0
|
|
207
242
|
};
|
|
208
243
|
|
|
244
|
+
const platform = normalizePlatform();
|
|
245
|
+
|
|
209
246
|
try {
|
|
210
|
-
if (
|
|
211
|
-
cache.l1d = parseInt(execSync('sysctl -n hw.l1dcachesize', { encoding: 'utf8', timeout: 5000 })) / 1024 || 0;
|
|
212
|
-
cache.l1i = parseInt(execSync('sysctl -n hw.l1icachesize', { encoding: 'utf8', timeout: 5000 })) / 1024 || 0;
|
|
213
|
-
cache.l2 = parseInt(execSync('sysctl -n hw.l2cachesize', { encoding: 'utf8', timeout: 5000 })) / 1024 / 1024 || 0;
|
|
214
|
-
cache.l3 = parseInt(execSync('sysctl -n hw.l3cachesize', { encoding: 'utf8', timeout: 5000 })) / 1024 / 1024 || 0;
|
|
215
|
-
} else if (
|
|
247
|
+
if (platform === 'darwin') {
|
|
248
|
+
cache.l1d = parseInt(childProcess.execSync('sysctl -n hw.l1dcachesize', { encoding: 'utf8', timeout: 5000 })) / 1024 || 0;
|
|
249
|
+
cache.l1i = parseInt(childProcess.execSync('sysctl -n hw.l1icachesize', { encoding: 'utf8', timeout: 5000 })) / 1024 || 0;
|
|
250
|
+
cache.l2 = parseInt(childProcess.execSync('sysctl -n hw.l2cachesize', { encoding: 'utf8', timeout: 5000 })) / 1024 / 1024 || 0;
|
|
251
|
+
cache.l3 = parseInt(childProcess.execSync('sysctl -n hw.l3cachesize', { encoding: 'utf8', timeout: 5000 })) / 1024 / 1024 || 0;
|
|
252
|
+
} else if (platform === 'linux') {
|
|
216
253
|
// Parse from /sys/devices/system/cpu/cpu0/cache/
|
|
217
254
|
const cachePath = '/sys/devices/system/cpu/cpu0/cache';
|
|
218
255
|
if (fs.existsSync(cachePath)) {
|
|
@@ -268,8 +305,10 @@ class CPUDetector {
|
|
|
268
305
|
bestSimd: 'none'
|
|
269
306
|
};
|
|
270
307
|
|
|
308
|
+
const platform = normalizePlatform();
|
|
309
|
+
|
|
271
310
|
try {
|
|
272
|
-
if (
|
|
311
|
+
if (platform === 'darwin') {
|
|
273
312
|
// For Apple Silicon (ARM64), use ARM features
|
|
274
313
|
if (process.arch === 'arm64') {
|
|
275
314
|
caps.neon = true; // All Apple Silicon has NEON
|
|
@@ -279,12 +318,12 @@ class CPUDetector {
|
|
|
279
318
|
} else {
|
|
280
319
|
// Intel Mac - check via sysctl
|
|
281
320
|
try {
|
|
282
|
-
const features = execSync('sysctl -n machdep.cpu.features', {
|
|
321
|
+
const features = childProcess.execSync('sysctl -n machdep.cpu.features', {
|
|
283
322
|
encoding: 'utf8',
|
|
284
323
|
timeout: 5000
|
|
285
324
|
}).toLowerCase();
|
|
286
325
|
|
|
287
|
-
const leafFeatures = execSync('sysctl -n machdep.cpu.leaf7_features', {
|
|
326
|
+
const leafFeatures = childProcess.execSync('sysctl -n machdep.cpu.leaf7_features', {
|
|
288
327
|
encoding: 'utf8',
|
|
289
328
|
timeout: 5000
|
|
290
329
|
}).toLowerCase();
|
|
@@ -308,7 +347,7 @@ class CPUDetector {
|
|
|
308
347
|
}
|
|
309
348
|
}
|
|
310
349
|
|
|
311
|
-
} else if (
|
|
350
|
+
} else if (platform === 'linux') {
|
|
312
351
|
// Linux - check /proc/cpuinfo
|
|
313
352
|
const cpuInfo = fs.readFileSync('/proc/cpuinfo', 'utf8').toLowerCase();
|
|
314
353
|
const flags = cpuInfo.match(/flags\s*:\s*(.+)/)?.[1] || '';
|
|
@@ -334,7 +373,7 @@ class CPUDetector {
|
|
|
334
373
|
caps.dotprod = flags.includes('asimddp');
|
|
335
374
|
}
|
|
336
375
|
|
|
337
|
-
} else if (
|
|
376
|
+
} else if (platform === 'win32') {
|
|
338
377
|
// Windows - use WMIC or assume based on CPU model
|
|
339
378
|
const cpuName = os.cpus()[0]?.model?.toLowerCase() || '';
|
|
340
379
|
|
|
@@ -15,6 +15,10 @@ class CUDADetector {
|
|
|
15
15
|
this.detectionMode = null;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
execCommand(command, options = {}) {
|
|
19
|
+
return execSync(command, options);
|
|
20
|
+
}
|
|
21
|
+
|
|
18
22
|
/**
|
|
19
23
|
* Check if CUDA is available
|
|
20
24
|
*/
|
|
@@ -43,7 +47,7 @@ class CUDADetector {
|
|
|
43
47
|
|
|
44
48
|
hasNvidiaSMI() {
|
|
45
49
|
try {
|
|
46
|
-
|
|
50
|
+
this.execCommand('nvidia-smi --version', {
|
|
47
51
|
encoding: 'utf8',
|
|
48
52
|
timeout: 5000,
|
|
49
53
|
stdio: ['pipe', 'pipe', 'pipe']
|
|
@@ -142,7 +146,7 @@ class CUDADetector {
|
|
|
142
146
|
}
|
|
143
147
|
|
|
144
148
|
try {
|
|
145
|
-
|
|
149
|
+
this.execCommand('nvcc --version', {
|
|
146
150
|
encoding: 'utf8',
|
|
147
151
|
timeout: 5000,
|
|
148
152
|
stdio: ['pipe', 'pipe', 'pipe']
|
|
@@ -197,17 +201,18 @@ class CUDADetector {
|
|
|
197
201
|
|
|
198
202
|
try {
|
|
199
203
|
// Get driver and CUDA version
|
|
200
|
-
const versionInfo =
|
|
204
|
+
const versionInfo = this.execCommand('nvidia-smi --query-gpu=driver_version --format=csv,noheader,nounits', {
|
|
201
205
|
encoding: 'utf8',
|
|
202
206
|
timeout: 5000
|
|
203
207
|
}).trim().split('\n')[0];
|
|
204
208
|
result.driver = versionInfo;
|
|
205
209
|
|
|
206
|
-
//
|
|
207
|
-
const
|
|
210
|
+
// Parse the nvidia-smi banner in JS so Windows does not require shell-only tools like `head`.
|
|
211
|
+
const banner = this.execCommand('nvidia-smi', {
|
|
208
212
|
encoding: 'utf8',
|
|
209
213
|
timeout: 5000
|
|
210
214
|
});
|
|
215
|
+
const header = banner.split('\n').slice(0, 3).join('\n');
|
|
211
216
|
const cudaMatch = header.match(/CUDA Version:\s*([\d.]+)/);
|
|
212
217
|
if (cudaMatch) {
|
|
213
218
|
result.cuda = cudaMatch[1];
|
|
@@ -237,7 +242,7 @@ class CUDADetector {
|
|
|
237
242
|
'clocks.max.sm'
|
|
238
243
|
].join(',');
|
|
239
244
|
|
|
240
|
-
const gpuData =
|
|
245
|
+
const gpuData = this.execCommand(
|
|
241
246
|
`nvidia-smi --query-gpu=${query} --format=csv,noheader,nounits`,
|
|
242
247
|
{ encoding: 'utf8', timeout: 10000 }
|
|
243
248
|
).trim();
|
|
@@ -286,7 +291,7 @@ class CUDADetector {
|
|
|
286
291
|
} catch (e) {
|
|
287
292
|
// Fallback to simpler query
|
|
288
293
|
try {
|
|
289
|
-
const simpleQuery =
|
|
294
|
+
const simpleQuery = this.execCommand(
|
|
290
295
|
'nvidia-smi --query-gpu=name,memory.total --format=csv,noheader,nounits',
|
|
291
296
|
{ encoding: 'utf8', timeout: 5000 }
|
|
292
297
|
).trim();
|
|
@@ -408,7 +413,7 @@ class CUDADetector {
|
|
|
408
413
|
}
|
|
409
414
|
|
|
410
415
|
try {
|
|
411
|
-
const nvccVersion =
|
|
416
|
+
const nvccVersion = this.execCommand('nvcc --version', {
|
|
412
417
|
encoding: 'utf8',
|
|
413
418
|
timeout: 5000,
|
|
414
419
|
stdio: ['pipe', 'pipe', 'pipe']
|