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 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
@@ -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 platform = os.platform();
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 service after install:',
550
- ' sudo systemctl start ollama',
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
- // Check if it's integrated GPU (should cap tier)
867
- const isIntegratedGPU = /iris.*xe|iris.*graphics|uhd.*graphics|vega.*integrated|radeon.*graphics|intel.*integrated|integrated/i.test(gpuModel);
868
- const hasDedicatedGPU = vramGB > 0 && !isIntegratedGPU;
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.4",
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 isPC = !isAppleSilicon && (process.platform === 'win32' || process.platform === 'linux');
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 (/iris xe|uhd.*graphics|vega.*integrated|radeon.*graphics/i.test(gpuModel)) {
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 gpu = gpuModel.toLowerCase();
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 && !gpuModel.toLowerCase().includes('iris') && !gpuModel.toLowerCase().includes('integrated')) {
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 (process.platform === 'darwin') {
94
+ if (platform === 'darwin') {
92
95
  return parseInt(execSync('sysctl -n hw.physicalcpu', { encoding: 'utf8', timeout: 5000 }).trim());
93
- } else if (process.platform === 'linux') {
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 (process.platform === 'win32') {
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 (process.platform === 'darwin') {
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 (process.platform === 'linux') {
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 (process.platform === 'win32') {
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 (process.platform === 'darwin') {
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 (process.platform === 'linux') {
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 (process.platform === 'darwin') {
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 (process.platform === 'linux') {
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 (process.platform === 'win32') {
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
 
@@ -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
- backendGPUs[0]?.memory?.total ||
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 = dedicatedBackendGPUs[0]?.name || backendGPUs[0]?.name || null;
294
- const modelFromUnified = summary.gpuInventory || summary.gpuModel || fallbackModel || systemInfo.gpu.model;
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: hasFallbackDedicatedGpu ? true : primaryType !== 'metal',
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: osInfo.platform || process.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 (process.platform === 'darwin' && process.arch === 'arm64') {
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 (process.platform === 'linux') {
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
- // Fallback GPU inventory via systeminformation (Windows/Linux) when no
129
- // accelerator backend is currently available (CUDA/ROCm/Metal/Intel).
130
- const hasAcceleratedBackend = Boolean(
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(primary.info.gpus);
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(primary.info.gpus);
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 = [{ name: summary.gpuModel, count: 1 }];
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(primary.info.gpus);
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 (result.systemGpu?.available && Array.isArray(result.systemGpu.gpus) && result.systemGpu.gpus.length > 0) {
283
- const inventory = this.summarizeGPUInventory(result.systemGpu.gpus);
287
+ if (inventorySource.length > 0) {
288
+ const inventory = this.summarizeGPUInventory(inventorySource);
284
289
  summary.totalVRAM = result.systemGpu.totalVRAM || 0;
285
- summary.gpuCount = result.systemGpu.gpus.length;
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 gpus) {
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
- if (process.platform === 'linux') {
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: process.platform === 'linux' ? 'systeminformation+lspci' : 'systeminformation',
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 isPC = !isAppleSilicon && (process.platform === 'win32' || process.platform === 'linux');
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
- // Detect if system has dedicated GPU (not integrated) - improved detection
1471
- const hasIntegratedGPU = /iris.*xe|iris.*graphics|uhd.*graphics|vega.*integrated|radeon.*graphics|intel.*integrated|integrated/i.test(gpuModel);
1472
- const hasDedicatedGPU = vramGB > 0 && !hasIntegratedGPU && !unified;
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 gpu = gpuModel.toLowerCase();
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
- return 'ollama --version || (brew install ollama)';
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') {
@@ -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 = isIntegratedGPU(gpuModel);
166
- const dedicatedGPU = vramGB > 0 && !integrated && !appleSilicon;
194
+ const integrated = detectIntegratedGpu(hardware, gpuModel);
195
+ const dedicatedGPU = detectDedicatedGpu(hardware, integrated, appleSilicon, vramGB);
167
196
 
168
197
  let baselineTPS7B;
169
198
  let backend;