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 +5 -0
- package/bin/enhanced_cli.js +318 -3
- package/package.json +10 -3
- package/src/hardware/backends/rocm-detector.js +52 -6
- package/src/hardware/detector.js +47 -11
- package/src/hardware/profiles.js +484 -0
- package/src/hardware/unified-detector.js +94 -1
- package/src/index.js +19 -1
- package/src/models/ai-check-selector.js +6 -0
- package/src/models/fine-tuning-support.js +215 -0
- package/src/ui/cli-theme.js +32 -85
- package/src/ui/interactive-panel.js +1 -0
- package/src/data/CLAUDE.md +0 -17
- package/src/hardware/CLAUDE.md +0 -18
- package/src/hardware/backends/CLAUDE.md +0 -17
- package/src/models/CLAUDE.md +0 -23
- package/src/ollama/CLAUDE.md +0 -30
- package/src/plugins/CLAUDE.md +0 -17
- package/src/utils/CLAUDE.md +0 -17
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
|
package/bin/enhanced_cli.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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": "^
|
|
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
|
-
|
|
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
|
@@ -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
|
-
|
|
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 =
|
|
251
|
-
|
|
252
|
-
|
|
270
|
+
const backendInfo = hasFallbackDedicatedGpu
|
|
271
|
+
? unified.systemGpu
|
|
272
|
+
: (unified.backends?.[primaryType]?.info || {});
|
|
253
273
|
|
|
254
|
-
const
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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) {
|