llm-checker 3.5.3 → 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 +23 -1
- package/bin/enhanced_cli.js +53 -8
- 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/backends/rocm-detector.js +52 -6
- package/src/hardware/detector.js +64 -18
- package/src/hardware/unified-detector.js +231 -24
- package/src/index.js +18 -7
- package/src/models/ai-check-selector.js +6 -0
- package/src/models/fine-tuning-support.js +215 -0
- 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
|
|
@@ -531,19 +548,24 @@ Hardware Tier: HIGH | Models Analyzed: 205
|
|
|
531
548
|
Coding:
|
|
532
549
|
qwen2.5-coder:14b (14B)
|
|
533
550
|
Score: 78/100
|
|
551
|
+
Fine-tuning: LoRA+QLoRA
|
|
534
552
|
Command: ollama pull qwen2.5-coder:14b
|
|
535
553
|
|
|
536
554
|
Reasoning:
|
|
537
555
|
deepseek-r1:14b (14B)
|
|
538
556
|
Score: 86/100
|
|
557
|
+
Fine-tuning: QLoRA
|
|
539
558
|
Command: ollama pull deepseek-r1:14b
|
|
540
559
|
|
|
541
560
|
Multimodal:
|
|
542
561
|
llama3.2-vision:11b (11B)
|
|
543
562
|
Score: 83/100
|
|
563
|
+
Fine-tuning: LoRA+QLoRA
|
|
544
564
|
Command: ollama pull llama3.2-vision:11b
|
|
545
565
|
```
|
|
546
566
|
|
|
567
|
+
`check`, `recommend`, and `ai-check` include a fine-tuning suitability label in output to help choose between Full FT, LoRA, and QLoRA paths.
|
|
568
|
+
|
|
547
569
|
### `search` — Model Search
|
|
548
570
|
|
|
549
571
|
```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,
|
|
@@ -23,6 +24,7 @@ const {
|
|
|
23
24
|
getRuntimeDisplayName,
|
|
24
25
|
getRuntimeCommandSet
|
|
25
26
|
} = require('../src/runtime/runtime-support');
|
|
27
|
+
const { evaluateFineTuningSupport } = require('../src/models/fine-tuning-support');
|
|
26
28
|
const { CalibrationManager } = require('../src/calibration/calibration-manager');
|
|
27
29
|
const { SUPPORTED_CALIBRATION_OBJECTIVES } = require('../src/calibration/schemas');
|
|
28
30
|
const {
|
|
@@ -512,8 +514,23 @@ if (!program.commands.some((cmd) => cmd.name() === 'help')) {
|
|
|
512
514
|
|
|
513
515
|
// Ollama installation helper
|
|
514
516
|
function getOllamaInstallInstructions() {
|
|
515
|
-
const
|
|
517
|
+
const rawPlatform = os.platform();
|
|
518
|
+
const platform = normalizePlatform(rawPlatform);
|
|
516
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
|
+
}
|
|
517
534
|
|
|
518
535
|
const instructions = {
|
|
519
536
|
'darwin': {
|
|
@@ -545,8 +562,8 @@ function getOllamaInstallInstructions() {
|
|
|
545
562
|
'1. Review official installation options:',
|
|
546
563
|
' https://github.com/ollama/ollama/blob/main/docs/linux.md',
|
|
547
564
|
'2. Prefer a package manager (apt/dnf/pacman) when available',
|
|
548
|
-
'3. Start
|
|
549
|
-
'
|
|
565
|
+
'3. Start Ollama after install:',
|
|
566
|
+
' ollama serve',
|
|
550
567
|
'4. Test with: ollama run llama2:7b'
|
|
551
568
|
],
|
|
552
569
|
alternativeInstall: 'Manual install: https://github.com/ollama/ollama/blob/main/docs/linux.md'
|
|
@@ -855,6 +872,13 @@ function calculateModelCompatibilityScore(model, hardware) {
|
|
|
855
872
|
return Math.max(0, Math.min(100, Math.round(score)));
|
|
856
873
|
}
|
|
857
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
|
+
|
|
858
882
|
// Helper function to get hardware tier for display
|
|
859
883
|
function getHardwareTierForDisplay(hardware) {
|
|
860
884
|
const ram = hardware.memory.total;
|
|
@@ -862,9 +886,15 @@ function getHardwareTierForDisplay(hardware) {
|
|
|
862
886
|
const gpuModel = hardware.gpu?.model || '';
|
|
863
887
|
const vramGB = hardware.gpu?.vram || 0;
|
|
864
888
|
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
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);
|
|
868
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'));
|
|
869
899
|
|
|
870
900
|
// Base tier calculation
|
|
@@ -931,6 +961,8 @@ function displaySystemInfo(hardware, analysis) {
|
|
|
931
961
|
const cpuColor = hardware.cpu.cores >= 8 ? chalk.green : hardware.cpu.cores >= 4 ? chalk.yellow : chalk.red;
|
|
932
962
|
const ramColor = hardware.memory.total >= 32 ? chalk.green : hardware.memory.total >= 16 ? chalk.yellow : chalk.red;
|
|
933
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);
|
|
934
966
|
|
|
935
967
|
const lines = [
|
|
936
968
|
`${chalk.cyan('CPU:')} ${cpuColor(hardware.cpu.brand)} ${chalk.gray(`(${hardware.cpu.cores} cores, ${hardware.cpu.speed}GHz)`)}`,
|
|
@@ -938,6 +970,8 @@ function displaySystemInfo(hardware, analysis) {
|
|
|
938
970
|
`${chalk.cyan('RAM:')} ${ramColor(hardware.memory.total + 'GB')}`,
|
|
939
971
|
`${chalk.cyan('GPU:')} ${gpuColor(hardware.gpu.model || 'Not detected')}`,
|
|
940
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)}`,
|
|
941
975
|
];
|
|
942
976
|
|
|
943
977
|
const tier = analysis.summary.hardwareTier?.replace('_', ' ').toUpperCase() || 'UNKNOWN';
|
|
@@ -1203,7 +1237,7 @@ function displayLegacyRecommendations(recommendations) {
|
|
|
1203
1237
|
console.log(chalk.cyan('╰'));
|
|
1204
1238
|
}
|
|
1205
1239
|
|
|
1206
|
-
function displayIntelligentRecommendations(intelligentData) {
|
|
1240
|
+
function displayIntelligentRecommendations(intelligentData, hardware = null) {
|
|
1207
1241
|
if (!intelligentData || !intelligentData.summary) return;
|
|
1208
1242
|
|
|
1209
1243
|
const { summary, recommendations } = intelligentData;
|
|
@@ -1220,10 +1254,12 @@ function displayIntelligentRecommendations(intelligentData) {
|
|
|
1220
1254
|
// Mostrar mejor modelo general
|
|
1221
1255
|
if (summary.best_overall) {
|
|
1222
1256
|
const best = summary.best_overall;
|
|
1257
|
+
const bestFineTuning = evaluateFineTuningSupport(best, hardware || {});
|
|
1223
1258
|
console.log(chalk.red('│') + ` ${chalk.bold.yellow('BEST OVERALL:')} ${chalk.green.bold(best.name)}`);
|
|
1224
1259
|
console.log(chalk.red('│') + ` Command: ${chalk.cyan.bold(best.command)}`);
|
|
1225
1260
|
console.log(chalk.red('│') + ` Score: ${chalk.yellow.bold(best.score)}/100 | Category: ${chalk.magenta(best.category)}`);
|
|
1226
1261
|
console.log(chalk.red('│') + ` Quantization: ${chalk.white.bold(best.quantization || 'Q4_K_M')}`);
|
|
1262
|
+
console.log(chalk.red('│') + ` Fine-tuning: ${chalk.blue.bold(bestFineTuning.shortLabel)}`);
|
|
1227
1263
|
console.log(chalk.red('│'));
|
|
1228
1264
|
}
|
|
1229
1265
|
|
|
@@ -1242,11 +1278,13 @@ function displayIntelligentRecommendations(intelligentData) {
|
|
|
1242
1278
|
const icon = categories[category] || 'Other';
|
|
1243
1279
|
const categoryName = category.charAt(0).toUpperCase() + category.slice(1);
|
|
1244
1280
|
const scoreColor = getScoreColor(model.score);
|
|
1281
|
+
const fineTuningSupport = evaluateFineTuningSupport(model, hardware || {});
|
|
1245
1282
|
|
|
1246
1283
|
console.log(chalk.red('│') + ` ${chalk.bold.white(categoryName)} (${icon}):`);
|
|
1247
1284
|
console.log(chalk.red('│') + ` ${chalk.green(model.name)} (${model.size})`);
|
|
1248
1285
|
console.log(chalk.red('│') + ` Score: ${scoreColor.bold(model.score)}/100 | Pulls: ${chalk.gray(model.pulls?.toLocaleString() || 'N/A')}`);
|
|
1249
1286
|
console.log(chalk.red('│') + ` Quantization: ${chalk.white.bold(model.quantization || 'Q4_K_M')}`);
|
|
1287
|
+
console.log(chalk.red('│') + ` Fine-tuning: ${chalk.blue.bold(fineTuningSupport.shortLabel)}`);
|
|
1250
1288
|
console.log(chalk.red('│') + ` Command: ${chalk.cyan.bold(model.command)}`);
|
|
1251
1289
|
console.log(chalk.red('│'));
|
|
1252
1290
|
});
|
|
@@ -2079,6 +2117,8 @@ async function displayModelRecommendations(analysis, hardware, useCase = 'genera
|
|
|
2079
2117
|
const realSize = getRealSizeFromOllamaCache(model) || estimateModelSize(model);
|
|
2080
2118
|
console.log(`Size: ${chalk.white(realSize)}`);
|
|
2081
2119
|
console.log(`Compatibility Score: ${chalk.green.bold(model.adjustedScore || model.score || 'N/A')}/100`);
|
|
2120
|
+
const fineTuningSupport = evaluateFineTuningSupport(model, hardware);
|
|
2121
|
+
console.log(`Fine-tuning: ${chalk.blue.bold(fineTuningSupport.shortLabel)}`);
|
|
2082
2122
|
|
|
2083
2123
|
if (index === 0) {
|
|
2084
2124
|
console.log(`Reason: ${chalk.gray(reason)}`);
|
|
@@ -3628,7 +3668,7 @@ Calibrated routing examples:
|
|
|
3628
3668
|
displaySystemInfo(hardware, { summary: { hardwareTier: intelligentRecommendations.summary.hardware_tier } });
|
|
3629
3669
|
|
|
3630
3670
|
// Mostrar recomendaciones
|
|
3631
|
-
displayIntelligentRecommendations(intelligentRecommendations);
|
|
3671
|
+
displayIntelligentRecommendations(intelligentRecommendations, hardware);
|
|
3632
3672
|
displayCalibratedRoutingDecision('recommend', calibratedPolicy, routeDecision, routingPreference.warnings);
|
|
3633
3673
|
|
|
3634
3674
|
if (policyConfig && policyEvaluation && policyEnforcement) {
|
|
@@ -5019,6 +5059,11 @@ program
|
|
|
5019
5059
|
console.log(` Tier: ${chalk.cyan(detector.getHardwareTier().replace('_', ' ').toUpperCase())}`);
|
|
5020
5060
|
console.log(` Max model size: ${chalk.green(detector.getMaxModelSize() + 'GB')}`);
|
|
5021
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
|
+
}
|
|
5022
5067
|
|
|
5023
5068
|
// CPU
|
|
5024
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
|
|
|
@@ -248,13 +248,20 @@ class ROCmDetector {
|
|
|
248
248
|
timeout: 10000
|
|
249
249
|
});
|
|
250
250
|
|
|
251
|
-
// Parse memory info
|
|
252
|
-
|
|
251
|
+
// Parse memory info. Newer rocm-smi reports bytes "(B)" while some
|
|
252
|
+
// systems expose MiB; normalize to GB safely.
|
|
253
253
|
const gpuMemory = {};
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
const
|
|
257
|
-
|
|
254
|
+
const memLines = String(memInfo || '').split('\n');
|
|
255
|
+
for (const line of memLines) {
|
|
256
|
+
const lineMatch = line.match(/GPU\[(\d+)\].*?Total.*?Memory\s*(?:\(([^)]+)\))?\s*:\s*(\d+)/i);
|
|
257
|
+
if (!lineMatch) continue;
|
|
258
|
+
|
|
259
|
+
const idx = parseInt(lineMatch[1], 10);
|
|
260
|
+
const unitHint = lineMatch[2] || '';
|
|
261
|
+
const rawValue = parseInt(lineMatch[3], 10);
|
|
262
|
+
|
|
263
|
+
if (!Number.isFinite(rawValue) || rawValue <= 0) continue;
|
|
264
|
+
gpuMemory[idx] = this.normalizeRocmMemoryToGB(rawValue, unitHint);
|
|
258
265
|
}
|
|
259
266
|
|
|
260
267
|
// Get temperature and utilization
|
|
@@ -312,6 +319,45 @@ class ROCmDetector {
|
|
|
312
319
|
}
|
|
313
320
|
}
|
|
314
321
|
|
|
322
|
+
/**
|
|
323
|
+
* Normalize rocm-smi memory values to GB.
|
|
324
|
+
* rocm-smi may report bytes "(B)" or MiB depending on version/system.
|
|
325
|
+
*/
|
|
326
|
+
normalizeRocmMemoryToGB(value, unitHint = '') {
|
|
327
|
+
const numericValue = Number(value);
|
|
328
|
+
if (!Number.isFinite(numericValue) || numericValue <= 0) {
|
|
329
|
+
return 0;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const unit = String(unitHint || '').toLowerCase();
|
|
333
|
+
|
|
334
|
+
if (unit.includes('gib') || unit.includes('gb')) {
|
|
335
|
+
return Math.round(numericValue);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (unit.includes('mib') || unit.includes('mb')) {
|
|
339
|
+
return Math.round(numericValue / 1024);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (unit.includes('kib') || unit.includes('kb')) {
|
|
343
|
+
return Math.round(numericValue / (1024 * 1024));
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (unit === 'b' || unit === 'bytes') {
|
|
347
|
+
return Math.round(numericValue / (1024 ** 3));
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Unit was not provided. Use value magnitude heuristics.
|
|
351
|
+
if (numericValue >= 1024 ** 3) {
|
|
352
|
+
return Math.round(numericValue / (1024 ** 3));
|
|
353
|
+
}
|
|
354
|
+
if (numericValue >= 1024) {
|
|
355
|
+
return Math.round(numericValue / 1024);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return Math.round(numericValue);
|
|
359
|
+
}
|
|
360
|
+
|
|
315
361
|
/**
|
|
316
362
|
* Detect GPUs via rocminfo
|
|
317
363
|
*/
|
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,33 +256,75 @@ class HardwareDetector {
|
|
|
255
256
|
}
|
|
256
257
|
|
|
257
258
|
const primaryType = unified.primary.type || 'cpu';
|
|
258
|
-
if (primaryType === 'cpu') {
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
259
|
const summary = unified.summary;
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
260
|
+
const hasFallbackDedicatedGpu = Boolean(
|
|
261
|
+
primaryType === 'cpu' &&
|
|
262
|
+
unified.systemGpu?.available &&
|
|
263
|
+
Array.isArray(unified.systemGpu.gpus) &&
|
|
264
|
+
unified.systemGpu.gpus.some((gpu) => gpu.type === 'dedicated')
|
|
265
|
+
);
|
|
266
|
+
const backendInfo = hasFallbackDedicatedGpu
|
|
267
|
+
? unified.systemGpu
|
|
268
|
+
: (unified.backends?.[primaryType]?.info || {});
|
|
270
269
|
|
|
271
|
-
const
|
|
270
|
+
const backendGPUs = Array.isArray(backendInfo.gpus) ? backendInfo.gpus : [];
|
|
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;
|
|
286
|
+
|
|
287
|
+
const gpuCount = summary.gpuCount ||
|
|
288
|
+
dedicatedBackendGPUs.length ||
|
|
289
|
+
backendGPUs.length ||
|
|
290
|
+
systemInfo.gpu.gpuCount ||
|
|
291
|
+
1;
|
|
292
|
+
|
|
293
|
+
const totalVRAMFromUnified = typeof summary.totalVRAM === 'number' ? summary.totalVRAM : 0;
|
|
294
|
+
const totalVRAMFromFallback = dedicatedBackendGPUs.reduce((sum, gpu) => {
|
|
295
|
+
const amount = Number(gpu?.memory?.total || gpu?.memoryTotal || 0);
|
|
296
|
+
return sum + (Number.isFinite(amount) ? amount : 0);
|
|
297
|
+
}, 0);
|
|
298
|
+
const totalVRAM = totalVRAMFromUnified || totalVRAMFromFallback || systemInfo.gpu.vram;
|
|
299
|
+
const perGPUVRAM = dedicatedBackendGPUs[0]?.memory?.total ||
|
|
300
|
+
(hasDedicatedGPU && gpuCount > 0 && totalVRAM > 0 ? Math.round(totalVRAM / Math.max(1, summary.dedicatedGpuCount || gpuCount)) : 0);
|
|
301
|
+
|
|
302
|
+
const fallbackModel = primaryDedicatedModel || primaryIntegratedModel || backendGPUs[0]?.name || null;
|
|
303
|
+
const modelFromUnified = summary.gpuModel || fallbackModel || systemInfo.gpu.model;
|
|
272
304
|
const vendor = this.inferVendorFromGPUModel(modelFromUnified, systemInfo.gpu.vendor);
|
|
305
|
+
const isAppleUnified = primaryType === 'metal';
|
|
306
|
+
|
|
307
|
+
systemInfo.summary = {
|
|
308
|
+
...summary
|
|
309
|
+
};
|
|
273
310
|
|
|
274
311
|
systemInfo.gpu = {
|
|
275
312
|
...systemInfo.gpu,
|
|
276
313
|
model: modelFromUnified,
|
|
277
314
|
vendor,
|
|
278
|
-
vram: totalVRAM || systemInfo.gpu.vram,
|
|
279
|
-
vramPerGPU: perGPUVRAM || systemInfo.gpu.vramPerGPU || 0,
|
|
280
|
-
dedicated:
|
|
281
|
-
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,
|
|
282
319
|
isMultiGPU: Boolean(summary.isMultiGPU || gpuCount > 1),
|
|
283
320
|
gpuInventory: summary.gpuInventory || null,
|
|
284
|
-
|
|
321
|
+
hasIntegratedGPU,
|
|
322
|
+
hasDedicatedGPU,
|
|
323
|
+
integratedGpuCount: summary.integratedGpuCount || 0,
|
|
324
|
+
dedicatedGpuCount: summary.dedicatedGpuCount || 0,
|
|
325
|
+
integratedGpuModels: integratedInventoryModels,
|
|
326
|
+
dedicatedGpuModels: dedicatedInventoryModels,
|
|
327
|
+
backend: hasFallbackDedicatedGpu ? 'generic' : primaryType,
|
|
285
328
|
driverVersion: backendInfo.driver || systemInfo.gpu.driverVersion
|
|
286
329
|
};
|
|
287
330
|
} catch (error) {
|
|
@@ -301,8 +344,11 @@ class HardwareDetector {
|
|
|
301
344
|
}
|
|
302
345
|
|
|
303
346
|
processOSInfo(osInfo) {
|
|
347
|
+
const rawPlatform = osInfo.platform || process.platform;
|
|
348
|
+
|
|
304
349
|
return {
|
|
305
|
-
platform:
|
|
350
|
+
platform: normalizePlatform(rawPlatform),
|
|
351
|
+
platformRaw: rawPlatform,
|
|
306
352
|
distro: osInfo.distro || 'Unknown',
|
|
307
353
|
release: osInfo.release || 'Unknown',
|
|
308
354
|
codename: osInfo.codename || 'Unknown',
|