llm-checker 3.5.2 → 3.5.4

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
@@ -531,19 +531,24 @@ Hardware Tier: HIGH | Models Analyzed: 205
531
531
  Coding:
532
532
  qwen2.5-coder:14b (14B)
533
533
  Score: 78/100
534
+ Fine-tuning: LoRA+QLoRA
534
535
  Command: ollama pull qwen2.5-coder:14b
535
536
 
536
537
  Reasoning:
537
538
  deepseek-r1:14b (14B)
538
539
  Score: 86/100
540
+ Fine-tuning: QLoRA
539
541
  Command: ollama pull deepseek-r1:14b
540
542
 
541
543
  Multimodal:
542
544
  llama3.2-vision:11b (11B)
543
545
  Score: 83/100
546
+ Fine-tuning: LoRA+QLoRA
544
547
  Command: ollama pull llama3.2-vision:11b
545
548
  ```
546
549
 
550
+ `check`, `recommend`, and `ai-check` include a fine-tuning suitability label in output to help choose between Full FT, LoRA, and QLoRA paths.
551
+
547
552
  ### `search` — Model Search
548
553
 
549
554
  ```bash
@@ -23,6 +23,7 @@ const {
23
23
  getRuntimeDisplayName,
24
24
  getRuntimeCommandSet
25
25
  } = require('../src/runtime/runtime-support');
26
+ const { evaluateFineTuningSupport } = require('../src/models/fine-tuning-support');
26
27
  const { CalibrationManager } = require('../src/calibration/calibration-manager');
27
28
  const { SUPPORTED_CALIBRATION_OBJECTIVES } = require('../src/calibration/schemas');
28
29
  const {
@@ -65,6 +66,7 @@ const COMMAND_HEADER_LABELS = {
65
66
  demo: 'Demo',
66
67
  ollama: 'Ollama Integration',
67
68
  recommend: 'Recommendations',
69
+ simulate: 'Hardware Simulation',
68
70
  'list-models': 'Model Catalog'
69
71
  };
70
72
 
@@ -1202,7 +1204,7 @@ function displayLegacyRecommendations(recommendations) {
1202
1204
  console.log(chalk.cyan('╰'));
1203
1205
  }
1204
1206
 
1205
- function displayIntelligentRecommendations(intelligentData) {
1207
+ function displayIntelligentRecommendations(intelligentData, hardware = null) {
1206
1208
  if (!intelligentData || !intelligentData.summary) return;
1207
1209
 
1208
1210
  const { summary, recommendations } = intelligentData;
@@ -1219,10 +1221,12 @@ function displayIntelligentRecommendations(intelligentData) {
1219
1221
  // Mostrar mejor modelo general
1220
1222
  if (summary.best_overall) {
1221
1223
  const best = summary.best_overall;
1224
+ const bestFineTuning = evaluateFineTuningSupport(best, hardware || {});
1222
1225
  console.log(chalk.red('│') + ` ${chalk.bold.yellow('BEST OVERALL:')} ${chalk.green.bold(best.name)}`);
1223
1226
  console.log(chalk.red('│') + ` Command: ${chalk.cyan.bold(best.command)}`);
1224
1227
  console.log(chalk.red('│') + ` Score: ${chalk.yellow.bold(best.score)}/100 | Category: ${chalk.magenta(best.category)}`);
1225
1228
  console.log(chalk.red('│') + ` Quantization: ${chalk.white.bold(best.quantization || 'Q4_K_M')}`);
1229
+ console.log(chalk.red('│') + ` Fine-tuning: ${chalk.blue.bold(bestFineTuning.shortLabel)}`);
1226
1230
  console.log(chalk.red('│'));
1227
1231
  }
1228
1232
 
@@ -1241,11 +1245,13 @@ function displayIntelligentRecommendations(intelligentData) {
1241
1245
  const icon = categories[category] || 'Other';
1242
1246
  const categoryName = category.charAt(0).toUpperCase() + category.slice(1);
1243
1247
  const scoreColor = getScoreColor(model.score);
1248
+ const fineTuningSupport = evaluateFineTuningSupport(model, hardware || {});
1244
1249
 
1245
1250
  console.log(chalk.red('│') + ` ${chalk.bold.white(categoryName)} (${icon}):`);
1246
1251
  console.log(chalk.red('│') + ` ${chalk.green(model.name)} (${model.size})`);
1247
1252
  console.log(chalk.red('│') + ` Score: ${scoreColor.bold(model.score)}/100 | Pulls: ${chalk.gray(model.pulls?.toLocaleString() || 'N/A')}`);
1248
1253
  console.log(chalk.red('│') + ` Quantization: ${chalk.white.bold(model.quantization || 'Q4_K_M')}`);
1254
+ console.log(chalk.red('│') + ` Fine-tuning: ${chalk.blue.bold(fineTuningSupport.shortLabel)}`);
1249
1255
  console.log(chalk.red('│') + ` Command: ${chalk.cyan.bold(model.command)}`);
1250
1256
  console.log(chalk.red('│'));
1251
1257
  });
@@ -2078,6 +2084,8 @@ async function displayModelRecommendations(analysis, hardware, useCase = 'genera
2078
2084
  const realSize = getRealSizeFromOllamaCache(model) || estimateModelSize(model);
2079
2085
  console.log(`Size: ${chalk.white(realSize)}`);
2080
2086
  console.log(`Compatibility Score: ${chalk.green.bold(model.adjustedScore || model.score || 'N/A')}/100`);
2087
+ const fineTuningSupport = evaluateFineTuningSupport(model, hardware);
2088
+ console.log(`Fine-tuning: ${chalk.blue.bold(fineTuningSupport.shortLabel)}`);
2081
2089
 
2082
2090
  if (index === 0) {
2083
2091
  console.log(`Reason: ${chalk.gray(reason)}`);
@@ -2938,6 +2946,11 @@ program
2938
2946
  .option('--performance-test', 'Run performance benchmarks')
2939
2947
  .option('--show-ollama-analysis', 'Show detailed Ollama model analysis')
2940
2948
  .option('--no-verbose', 'Disable step-by-step progress display')
2949
+ .option('--simulate <profile>', 'Simulate a hardware profile instead of detecting real hardware (use "list" to see profiles)')
2950
+ .option('--gpu <model>', 'Custom GPU model for simulation (e.g., "RTX 5060", "RX 7800 XT")')
2951
+ .option('--ram <gb>', 'Custom RAM in GB for simulation (e.g., 32)')
2952
+ .option('--cpu <model>', 'Custom CPU model for simulation (e.g., "AMD Ryzen 7 5700X")')
2953
+ .option('--vram <gb>', 'Override GPU VRAM in GB for simulation (auto-detected if omitted)')
2941
2954
  .addHelpText(
2942
2955
  'after',
2943
2956
  `
@@ -2946,6 +2959,12 @@ Enterprise policy examples:
2946
2959
  $ llm-checker check --policy ./policy.yaml --use-case coding --runtime vllm
2947
2960
  $ llm-checker check --policy ./policy.yaml --include-cloud --max-size 24B
2948
2961
 
2962
+ Hardware simulation:
2963
+ $ llm-checker check --simulate list
2964
+ $ llm-checker check --simulate rtx4090
2965
+ $ llm-checker check --simulate m4pro24 --use-case coding
2966
+ $ llm-checker check --gpu "RTX 5060" --ram 32 --cpu "AMD Ryzen 7 5700X"
2967
+
2949
2968
  Policy scope:
2950
2969
  - Evaluates all compatible and marginal candidates discovered during analysis
2951
2970
  - Not limited to the top --limit results shown in output
@@ -2958,7 +2977,57 @@ Policy scope:
2958
2977
  const verboseEnabled = options.verbose !== false;
2959
2978
  const checker = new (getLLMChecker())({ verbose: verboseEnabled });
2960
2979
  const policyConfig = options.policy ? loadPolicyConfiguration(options.policy) : null;
2961
-
2980
+
2981
+ // Handle hardware simulation (preset profile or custom flags)
2982
+ const hasCustomHwFlags = options.gpu || options.ram || options.cpu || options.vram;
2983
+ if (options.simulate || hasCustomHwFlags) {
2984
+ const { buildFullHardwareObject, buildCustomHardwareObject, getProfile, listProfiles } = require('../src/hardware/profiles');
2985
+ if (options.simulate === 'list') {
2986
+ console.log(chalk.cyan.bold('\n Available Hardware Profiles:\n'));
2987
+ listProfiles().forEach(line => console.log(line));
2988
+ console.log('');
2989
+ return;
2990
+ }
2991
+ let simulatedHardware;
2992
+ let displayLabel;
2993
+ if (hasCustomHwFlags) {
2994
+ const ramValue = options.ram ? parseInt(options.ram) : undefined;
2995
+ const vramValue = options.vram ? parseInt(options.vram) : undefined;
2996
+ if (options.vram && !options.gpu) {
2997
+ console.error(chalk.red('\n --vram requires --gpu in custom hardware mode (e.g., --gpu "RTX 4090" --vram 24).'));
2998
+ process.exit(1);
2999
+ }
3000
+ if (options.ram && (!Number.isFinite(ramValue) || ramValue <= 0)) {
3001
+ console.error(chalk.red(`\n Invalid --ram value: "${options.ram}". Must be a positive number (e.g., 32).`));
3002
+ process.exit(1);
3003
+ }
3004
+ if (options.vram && (!Number.isFinite(vramValue) || vramValue <= 0)) {
3005
+ console.error(chalk.red(`\n Invalid --vram value: "${options.vram}". Must be a positive number (e.g., 8).`));
3006
+ process.exit(1);
3007
+ }
3008
+ simulatedHardware = buildCustomHardwareObject({
3009
+ gpu: options.gpu || null,
3010
+ ram: ramValue,
3011
+ cpu: options.cpu || null,
3012
+ vram: vramValue
3013
+ });
3014
+ displayLabel = simulatedHardware._displayName;
3015
+ } else {
3016
+ const profile = getProfile(options.simulate);
3017
+ if (!profile) {
3018
+ console.error(chalk.red(`\n Unknown profile: ${options.simulate}`));
3019
+ console.log(chalk.gray('\n Available profiles:'));
3020
+ listProfiles().forEach(line => console.log(line));
3021
+ console.log('');
3022
+ process.exit(1);
3023
+ }
3024
+ simulatedHardware = buildFullHardwareObject(options.simulate);
3025
+ displayLabel = profile.displayName;
3026
+ }
3027
+ checker.setSimulatedHardware(simulatedHardware);
3028
+ console.log(chalk.magenta.bold(`\n SIMULATION MODE: ${displayLabel}\n`));
3029
+ }
3030
+
2962
3031
  // If verbose is disabled, show simple loading message
2963
3032
  if (!verboseEnabled) {
2964
3033
  process.stdout.write(chalk.gray('Analyzing your system...'));
@@ -3429,6 +3498,11 @@ program
3429
3498
  .option('--optimize <profile>', 'Optimization profile (balanced|speed|quality|context|coding)', 'balanced')
3430
3499
  .option('--no-verbose', 'Disable step-by-step progress display')
3431
3500
  .option('--policy <file>', 'Evaluate recommendations against a policy file')
3501
+ .option('--simulate <profile>', 'Simulate a hardware profile instead of detecting real hardware (use "list" to see profiles)')
3502
+ .option('--gpu <model>', 'Custom GPU model for simulation (e.g., "RTX 5060", "RX 7800 XT")')
3503
+ .option('--ram <gb>', 'Custom RAM in GB for simulation (e.g., 32)')
3504
+ .option('--cpu <model>', 'Custom CPU model for simulation (e.g., "AMD Ryzen 7 5700X")')
3505
+ .option('--vram <gb>', 'Override GPU VRAM in GB for simulation (auto-detected if omitted)')
3432
3506
  .option(
3433
3507
  '--calibrated [file]',
3434
3508
  'Use calibrated routing policy (optional file path; defaults to ~/.llm-checker/calibration-policy.{yaml,yml,json})'
@@ -3441,6 +3515,11 @@ Enterprise policy examples:
3441
3515
  $ llm-checker recommend --policy ./policy.yaml --category coding
3442
3516
  $ llm-checker recommend --policy ./policy.yaml --no-verbose
3443
3517
 
3518
+ Hardware simulation:
3519
+ $ llm-checker recommend --simulate rtx4090
3520
+ $ llm-checker recommend --simulate m4pro24 --category coding
3521
+ $ llm-checker recommend --gpu "RTX 5060" --ram 32 --cpu "AMD Ryzen 7 5700X"
3522
+
3444
3523
  Calibrated routing examples:
3445
3524
  $ llm-checker recommend --calibrated --category coding
3446
3525
  $ llm-checker recommend --calibrated ./calibration-policy.yaml --category reasoning
@@ -3452,6 +3531,57 @@ Calibrated routing examples:
3452
3531
  try {
3453
3532
  const verboseEnabled = options.verbose !== false;
3454
3533
  const checker = new (getLLMChecker())({ verbose: verboseEnabled });
3534
+
3535
+ // Handle hardware simulation (preset profile or custom flags)
3536
+ const hasCustomHwFlags = options.gpu || options.ram || options.cpu || options.vram;
3537
+ if (options.simulate || hasCustomHwFlags) {
3538
+ const { buildFullHardwareObject, buildCustomHardwareObject, getProfile, listProfiles } = require('../src/hardware/profiles');
3539
+ if (options.simulate === 'list') {
3540
+ console.log(chalk.cyan.bold('\n Available Hardware Profiles:\n'));
3541
+ listProfiles().forEach(line => console.log(line));
3542
+ console.log('');
3543
+ return;
3544
+ }
3545
+ let simulatedHardware;
3546
+ let displayLabel;
3547
+ if (hasCustomHwFlags) {
3548
+ const ramValue = options.ram ? parseInt(options.ram) : undefined;
3549
+ const vramValue = options.vram ? parseInt(options.vram) : undefined;
3550
+ if (options.vram && !options.gpu) {
3551
+ console.error(chalk.red('\n --vram requires --gpu in custom hardware mode (e.g., --gpu "RTX 4090" --vram 24).'));
3552
+ process.exit(1);
3553
+ }
3554
+ if (options.ram && (!Number.isFinite(ramValue) || ramValue <= 0)) {
3555
+ console.error(chalk.red(`\n Invalid --ram value: "${options.ram}". Must be a positive number (e.g., 32).`));
3556
+ process.exit(1);
3557
+ }
3558
+ if (options.vram && (!Number.isFinite(vramValue) || vramValue <= 0)) {
3559
+ console.error(chalk.red(`\n Invalid --vram value: "${options.vram}". Must be a positive number (e.g., 8).`));
3560
+ process.exit(1);
3561
+ }
3562
+ simulatedHardware = buildCustomHardwareObject({
3563
+ gpu: options.gpu || null,
3564
+ ram: ramValue,
3565
+ cpu: options.cpu || null,
3566
+ vram: vramValue
3567
+ });
3568
+ displayLabel = simulatedHardware._displayName;
3569
+ } else {
3570
+ const profile = getProfile(options.simulate);
3571
+ if (!profile) {
3572
+ console.error(chalk.red(`\n Unknown profile: ${options.simulate}`));
3573
+ console.log(chalk.gray('\n Available profiles:'));
3574
+ listProfiles().forEach(line => console.log(line));
3575
+ console.log('');
3576
+ process.exit(1);
3577
+ }
3578
+ simulatedHardware = buildFullHardwareObject(options.simulate);
3579
+ displayLabel = profile.displayName;
3580
+ }
3581
+ checker.setSimulatedHardware(simulatedHardware);
3582
+ console.log(chalk.magenta.bold(`\n SIMULATION MODE: ${displayLabel}\n`));
3583
+ }
3584
+
3455
3585
  const routingPreference = resolveRoutingPolicyPreference({
3456
3586
  policyOption: options.policy,
3457
3587
  calibratedOption: options.calibrated,
@@ -3505,7 +3635,7 @@ Calibrated routing examples:
3505
3635
  displaySystemInfo(hardware, { summary: { hardwareTier: intelligentRecommendations.summary.hardware_tier } });
3506
3636
 
3507
3637
  // Mostrar recomendaciones
3508
- displayIntelligentRecommendations(intelligentRecommendations);
3638
+ displayIntelligentRecommendations(intelligentRecommendations, hardware);
3509
3639
  displayCalibratedRoutingDecision('recommend', calibratedPolicy, routeDecision, routingPreference.warnings);
3510
3640
 
3511
3641
  if (policyConfig && policyEvaluation && policyEnforcement) {
@@ -3524,6 +3654,191 @@ Calibrated routing examples:
3524
3654
  }
3525
3655
  });
3526
3656
 
3657
+ program
3658
+ .command('simulate')
3659
+ .description('Simulate hardware profiles to see compatible LLM models for different systems')
3660
+ .option('-p, --profile <name>', 'Hardware profile to simulate (e.g., rtx4090, m4pro24, h100)')
3661
+ .option('-l, --list', 'List all available hardware profiles')
3662
+ .option('--gpu <model>', 'Custom GPU model (e.g., "RTX 5060", "RX 7800 XT", "Apple M4 Pro")')
3663
+ .option('--ram <gb>', 'Custom RAM in GB (e.g., 32)')
3664
+ .option('--cpu <model>', 'Custom CPU model (e.g., "AMD Ryzen 7 5700X")')
3665
+ .option('--vram <gb>', 'Override GPU VRAM in GB (auto-detected from GPU model if omitted)')
3666
+ .option('-u, --use-case <case>', 'Specify use case', 'general')
3667
+ .option('--optimize <profile>', 'Optimization profile (balanced|speed|quality|context|coding)', 'balanced')
3668
+ .option('--limit <number>', 'Number of compatible models to show (default: 1)', '1')
3669
+ .option('--no-verbose', 'Disable step-by-step progress display')
3670
+ .addHelpText(
3671
+ 'after',
3672
+ `
3673
+ Preset profiles:
3674
+ $ llm-checker simulate --list
3675
+ $ llm-checker simulate
3676
+ $ llm-checker simulate -p rtx4090
3677
+ $ llm-checker simulate -p m4pro24 --use-case coding
3678
+
3679
+ Custom hardware:
3680
+ $ llm-checker simulate --gpu "RTX 5060" --ram 32 --cpu "AMD Ryzen 7 5700X"
3681
+ $ llm-checker simulate --gpu "RTX 4090" --ram 64
3682
+ $ llm-checker simulate --gpu "RX 7800 XT" --ram 32 --vram 16
3683
+ $ llm-checker simulate --ram 16
3684
+ `
3685
+ )
3686
+ .action(async (options) => {
3687
+ const { buildFullHardwareObject, buildCustomHardwareObject, getProfile, getProfilesByCategory, listProfiles, CATEGORY_LABELS } = require('../src/hardware/profiles');
3688
+
3689
+ // List mode
3690
+ if (options.list) {
3691
+ console.log(chalk.cyan.bold('\n Available Hardware Profiles:\n'));
3692
+ listProfiles().forEach(line => console.log(line));
3693
+ console.log('');
3694
+ return;
3695
+ }
3696
+
3697
+ let simulatedHardware;
3698
+ let displayLabel;
3699
+
3700
+ // Custom hardware mode: --gpu, --ram, --cpu, --vram
3701
+ const hasCustomFlags = options.gpu || options.ram || options.cpu || options.vram;
3702
+ if (hasCustomFlags) {
3703
+ const ramValue = options.ram ? parseInt(options.ram) : undefined;
3704
+ const vramValue = options.vram ? parseInt(options.vram) : undefined;
3705
+ if (options.vram && !options.gpu) {
3706
+ console.error(chalk.red('\n --vram requires --gpu in custom hardware mode (e.g., --gpu "RTX 4090" --vram 24).'));
3707
+ process.exit(1);
3708
+ }
3709
+ if (options.ram && (!Number.isFinite(ramValue) || ramValue <= 0)) {
3710
+ console.error(chalk.red(`\n Invalid --ram value: "${options.ram}". Must be a positive number (e.g., 32).`));
3711
+ process.exit(1);
3712
+ }
3713
+ if (options.vram && (!Number.isFinite(vramValue) || vramValue <= 0)) {
3714
+ console.error(chalk.red(`\n Invalid --vram value: "${options.vram}". Must be a positive number (e.g., 8).`));
3715
+ process.exit(1);
3716
+ }
3717
+ simulatedHardware = buildCustomHardwareObject({
3718
+ gpu: options.gpu || null,
3719
+ ram: ramValue,
3720
+ cpu: options.cpu || null,
3721
+ vram: vramValue
3722
+ });
3723
+ displayLabel = simulatedHardware._displayName;
3724
+ } else {
3725
+ // Preset profile mode
3726
+ if (!options.profile) {
3727
+ // Guard against non-interactive environments
3728
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
3729
+ console.error(chalk.red('\n No hardware profile specified.'));
3730
+ console.log(chalk.gray(' Use --profile <name>, --gpu/--ram/--cpu flags, or --list to see profiles.\n'));
3731
+ process.exit(1);
3732
+ }
3733
+ // Interactive selection
3734
+ try {
3735
+ const inquirer = require('inquirer');
3736
+ const categories = getProfilesByCategory();
3737
+ const choices = [];
3738
+
3739
+ for (const [category, profiles] of Object.entries(categories)) {
3740
+ const label = CATEGORY_LABELS[category] || category;
3741
+ choices.push(new inquirer.Separator(chalk.gray(`── ${label} ──`)));
3742
+ for (const [key, profile] of Object.entries(profiles)) {
3743
+ const vramLabel = profile.gpu.unified
3744
+ ? `${profile.memory.total}GB unified`
3745
+ : (profile.gpu.vram > 0 ? `${profile.gpu.vram}GB VRAM` : 'No GPU');
3746
+ const ramLabel = profile.gpu.unified ? '' : ` / ${profile.memory.total}GB RAM`;
3747
+ choices.push({
3748
+ name: `${profile.displayName} ${chalk.gray(`(${vramLabel}${ramLabel})`)}`,
3749
+ value: key
3750
+ });
3751
+ }
3752
+ }
3753
+
3754
+ const { selectedProfile } = await inquirer.prompt([{
3755
+ type: 'list',
3756
+ name: 'selectedProfile',
3757
+ message: 'Select a hardware profile to simulate:',
3758
+ choices,
3759
+ pageSize: 20
3760
+ }]);
3761
+ options.profile = selectedProfile;
3762
+ } catch (error) {
3763
+ if (error.isTtyError) {
3764
+ console.error(chalk.red('Interactive mode requires a TTY terminal.'));
3765
+ console.log(chalk.gray('Use --profile <name>, --gpu/--ram flags, or --list to see available profiles.'));
3766
+ process.exit(1);
3767
+ }
3768
+ throw error;
3769
+ }
3770
+ }
3771
+
3772
+ // Validate profile
3773
+ const profile = getProfile(options.profile);
3774
+ if (!profile) {
3775
+ console.error(chalk.red(`\n Unknown profile: ${options.profile}`));
3776
+ console.log(chalk.gray('\n Available profiles:'));
3777
+ listProfiles().forEach(line => console.log(line));
3778
+ console.log('');
3779
+ process.exit(1);
3780
+ }
3781
+
3782
+ simulatedHardware = buildFullHardwareObject(options.profile);
3783
+ displayLabel = profile.displayName;
3784
+ }
3785
+
3786
+ showAsciiArt('simulate');
3787
+
3788
+ try {
3789
+ const verboseEnabled = options.verbose !== false;
3790
+ const checker = new (getLLMChecker())({ verbose: verboseEnabled });
3791
+ checker.setSimulatedHardware(simulatedHardware);
3792
+
3793
+ console.log(chalk.magenta.bold(` SIMULATION MODE: ${displayLabel}\n`));
3794
+
3795
+ if (!verboseEnabled) {
3796
+ process.stdout.write(chalk.gray('Analyzing simulated hardware...'));
3797
+ }
3798
+
3799
+ const hardware = await checker.getSystemInfo();
3800
+
3801
+ const normalizeUseCase = (useCase = '') => {
3802
+ const alias = useCase.toLowerCase().trim();
3803
+ const useCaseMap = {
3804
+ 'embed': 'embeddings', 'embedding': 'embeddings', 'embeddings': 'embeddings',
3805
+ 'embedings': 'embeddings', 'talk': 'chat', 'chat': 'chat', 'talking': 'chat'
3806
+ };
3807
+ return useCaseMap[alias] || alias || 'general';
3808
+ };
3809
+
3810
+ const analysis = await checker.analyze({
3811
+ useCase: normalizeUseCase(options.useCase),
3812
+ limit: parseInt(options.limit) || 10,
3813
+ runtime: 'ollama'
3814
+ });
3815
+
3816
+ if (!verboseEnabled) {
3817
+ console.log(chalk.green(' done'));
3818
+ }
3819
+
3820
+ displaySimplifiedSystemInfo(hardware);
3821
+
3822
+ const normalizedUseCase = normalizeUseCase(options.useCase);
3823
+ const limit = parseInt(options.limit) || 1;
3824
+ const recommendedModels = await displayModelRecommendations(
3825
+ analysis,
3826
+ hardware,
3827
+ normalizedUseCase,
3828
+ limit,
3829
+ 'ollama'
3830
+ );
3831
+ await displayQuickStartCommands(analysis, recommendedModels[0], recommendedModels, 'ollama');
3832
+
3833
+ } catch (error) {
3834
+ console.error(chalk.red('\nError:'), error.message);
3835
+ if (process.env.DEBUG) {
3836
+ console.error(error.stack);
3837
+ }
3838
+ process.exit(1);
3839
+ }
3840
+ });
3841
+
3527
3842
  program
3528
3843
  .command('list-models')
3529
3844
  .description('List all models from Ollama database')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "llm-checker",
3
- "version": "3.5.2",
3
+ "version": "3.5.4",
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",
@@ -47,7 +47,7 @@
47
47
  "inquirer": "^8.2.6",
48
48
  "node-fetch": "^2.7.0",
49
49
  "ora": "^5.4.1",
50
- "systeminformation": "^5.21.0",
50
+ "systeminformation": "^5.31.1",
51
51
  "table": "^6.8.1",
52
52
  "yaml": "^2.8.1",
53
53
  "zod": "^3.23.0"
@@ -55,9 +55,16 @@
55
55
  "optionalDependencies": {
56
56
  "sql.js": "^1.14.0"
57
57
  },
58
+ "overrides": {
59
+ "ajv": "^8.18.0",
60
+ "hono": "^4.11.10",
61
+ "glob": "^13.0.0",
62
+ "minimatch": "^10.2.2",
63
+ "test-exclude": "^7.0.1"
64
+ },
58
65
  "devDependencies": {
59
66
  "@types/node": "^20.0.0",
60
- "jest": "^29.7.0"
67
+ "jest": "^30.2.0"
61
68
  },
62
69
  "keywords": [
63
70
  "llm",
@@ -248,13 +248,20 @@ class ROCmDetector {
248
248
  timeout: 10000
249
249
  });
250
250
 
251
- // Parse memory info
252
- const memMatches = memInfo.matchAll(/GPU\[(\d+)\].*?Total.*?:\s*(\d+)/g);
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
- for (const match of memMatches) {
255
- const idx = parseInt(match[1]);
256
- const memMB = parseInt(match[2]);
257
- gpuMemory[idx] = Math.round(memMB / 1024); // Convert to GB
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
  */
@@ -7,9 +7,22 @@ class HardwareDetector {
7
7
  this.cacheExpiry = 5 * 60 * 1000;
8
8
  this.cacheTime = 0;
9
9
  this.unifiedDetector = new UnifiedDetector();
10
+ this._simulatedHardware = null;
11
+ }
12
+
13
+ setSimulatedHardware(hardwareObject) {
14
+ this._simulatedHardware = hardwareObject;
15
+ }
16
+
17
+ clearSimulatedHardware() {
18
+ this._simulatedHardware = null;
10
19
  }
11
20
 
12
21
  async getSystemInfo(forceFresh = false) {
22
+ // Return simulated hardware if set (bypasses real detection)
23
+ if (this._simulatedHardware) {
24
+ return this._simulatedHardware;
25
+ }
13
26
 
14
27
  if (!forceFresh && this.cache && (Date.now() - this.cacheTime < this.cacheExpiry)) {
15
28
  return this.cache;
@@ -242,20 +255,43 @@ class HardwareDetector {
242
255
  }
243
256
 
244
257
  const primaryType = unified.primary.type || 'cpu';
245
- if (primaryType === 'cpu') {
258
+ const hasFallbackDedicatedGpu = Boolean(
259
+ primaryType === 'cpu' &&
260
+ unified.systemGpu?.available &&
261
+ Array.isArray(unified.systemGpu.gpus) &&
262
+ unified.systemGpu.gpus.some((gpu) => gpu.type === 'dedicated')
263
+ );
264
+
265
+ if (primaryType === 'cpu' && !hasFallbackDedicatedGpu) {
246
266
  return;
247
267
  }
248
268
 
249
269
  const summary = unified.summary;
250
- const backendInfo = unified.backends?.[primaryType]?.info || {};
251
- const backendGPUs = Array.isArray(backendInfo.gpus) ? backendInfo.gpus : [];
252
- const gpuCount = summary.gpuCount || backendGPUs.length || systemInfo.gpu.gpuCount || 1;
270
+ const backendInfo = hasFallbackDedicatedGpu
271
+ ? unified.systemGpu
272
+ : (unified.backends?.[primaryType]?.info || {});
253
273
 
254
- const totalVRAM = typeof summary.totalVRAM === 'number' ? summary.totalVRAM : systemInfo.gpu.vram;
255
- const perGPUVRAM = backendGPUs[0]?.memory?.total
256
- || (gpuCount > 0 && totalVRAM > 0 ? Math.round(totalVRAM / gpuCount) : 0);
257
-
258
- const modelFromUnified = summary.gpuInventory || summary.gpuModel || systemInfo.gpu.model;
274
+ const backendGPUs = Array.isArray(backendInfo.gpus) ? backendInfo.gpus : [];
275
+ const dedicatedBackendGPUs = backendGPUs.filter((gpu) => gpu?.type !== 'integrated');
276
+
277
+ const gpuCount = summary.gpuCount ||
278
+ dedicatedBackendGPUs.length ||
279
+ backendGPUs.length ||
280
+ systemInfo.gpu.gpuCount ||
281
+ 1;
282
+
283
+ const totalVRAMFromUnified = typeof summary.totalVRAM === 'number' ? summary.totalVRAM : 0;
284
+ const totalVRAMFromFallback = dedicatedBackendGPUs.reduce((sum, gpu) => {
285
+ const amount = Number(gpu?.memory?.total || gpu?.memoryTotal || 0);
286
+ return sum + (Number.isFinite(amount) ? amount : 0);
287
+ }, 0);
288
+ const totalVRAM = totalVRAMFromUnified || totalVRAMFromFallback || systemInfo.gpu.vram;
289
+ const perGPUVRAM = dedicatedBackendGPUs[0]?.memory?.total ||
290
+ backendGPUs[0]?.memory?.total ||
291
+ (gpuCount > 0 && totalVRAM > 0 ? Math.round(totalVRAM / gpuCount) : 0);
292
+
293
+ const fallbackModel = dedicatedBackendGPUs[0]?.name || backendGPUs[0]?.name || null;
294
+ const modelFromUnified = summary.gpuInventory || summary.gpuModel || fallbackModel || systemInfo.gpu.model;
259
295
  const vendor = this.inferVendorFromGPUModel(modelFromUnified, systemInfo.gpu.vendor);
260
296
 
261
297
  systemInfo.gpu = {
@@ -264,11 +300,11 @@ class HardwareDetector {
264
300
  vendor,
265
301
  vram: totalVRAM || systemInfo.gpu.vram,
266
302
  vramPerGPU: perGPUVRAM || systemInfo.gpu.vramPerGPU || 0,
267
- dedicated: primaryType !== 'metal',
303
+ dedicated: hasFallbackDedicatedGpu ? true : primaryType !== 'metal',
268
304
  gpuCount,
269
305
  isMultiGPU: Boolean(summary.isMultiGPU || gpuCount > 1),
270
306
  gpuInventory: summary.gpuInventory || null,
271
- backend: primaryType,
307
+ backend: hasFallbackDedicatedGpu ? 'generic' : primaryType,
272
308
  driverVersion: backendInfo.driver || systemInfo.gpu.driverVersion
273
309
  };
274
310
  } catch (error) {