llm-checker 3.5.12 → 3.5.13
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 +83 -17
- package/bin/cli.js +40 -0
- package/bin/enhanced_cli.js +360 -33
- package/package.json +2 -1
- package/src/ai/model-selector.js +47 -16
- package/src/ai/multi-objective-selector.js +55 -9
- package/src/data/model-database.js +92 -1
- package/src/data/seed/README.md +8 -0
- package/src/data/seed/models.db +0 -0
- package/src/hardware/backends/rocm-detector.js +469 -68
- package/src/hardware/unified-detector.js +39 -5
- package/src/index.js +40 -7
- package/src/models/ai-check-selector.js +27 -2
- package/src/models/deterministic-selector.js +80 -7
- package/src/ollama/client.js +121 -0
- package/src/ollama/enhanced-scraper.js +40 -26
- package/src/ollama/native-scraper.js +52 -27
- package/src/ui/cli-theme.js +139 -24
- package/src/ui/interactive-panel.js +1 -18
- package/src/utils/verbose-progress.js +144 -187
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
const { execSync } = require('child_process');
|
|
9
9
|
const fs = require('fs');
|
|
10
|
+
const os = require('os');
|
|
10
11
|
const path = require('path');
|
|
11
12
|
|
|
12
13
|
class ROCmDetector {
|
|
@@ -20,6 +21,9 @@ class ROCmDetector {
|
|
|
20
21
|
static AMD_DEVICE_IDS = {
|
|
21
22
|
// RDNA 4 / Radeon AI PRO
|
|
22
23
|
'7551': { name: 'AMD Radeon AI PRO R9700', vram: 32 },
|
|
24
|
+
'7590': { name: 'AMD Radeon RX 9060 XT', vram: 16 },
|
|
25
|
+
'7580': { name: 'AMD Radeon RX 9070 XT', vram: 16 },
|
|
26
|
+
'7581': { name: 'AMD Radeon RX 9070', vram: 16 },
|
|
23
27
|
// RDNA 3 (RX 7000 series)
|
|
24
28
|
'744c': { name: 'AMD Radeon RX 7900 XTX', vram: 24 },
|
|
25
29
|
'7448': { name: 'AMD Radeon RX 7900 XT', vram: 20 },
|
|
@@ -170,6 +174,7 @@ class ROCmDetector {
|
|
|
170
174
|
gpus: [],
|
|
171
175
|
rocmVersion: null,
|
|
172
176
|
totalVRAM: 0,
|
|
177
|
+
totalSharedMemory: 0,
|
|
173
178
|
backend: 'rocm',
|
|
174
179
|
isMultiGPU: false,
|
|
175
180
|
speedCoefficient: 0
|
|
@@ -235,12 +240,7 @@ class ROCmDetector {
|
|
|
235
240
|
timeout: 10000
|
|
236
241
|
});
|
|
237
242
|
|
|
238
|
-
|
|
239
|
-
const gpuNames = [];
|
|
240
|
-
const nameMatches = gpuList.matchAll(/GPU\[(\d+)\].*?:\s*(.+)/g);
|
|
241
|
-
for (const match of nameMatches) {
|
|
242
|
-
gpuNames[parseInt(match[1])] = match[2].trim();
|
|
243
|
-
}
|
|
243
|
+
const gpuNames = this.parseRocmSmiProductNames(gpuList);
|
|
244
244
|
|
|
245
245
|
// Get VRAM info
|
|
246
246
|
const memInfo = execSync('rocm-smi --showmeminfo vram', {
|
|
@@ -248,21 +248,7 @@ class ROCmDetector {
|
|
|
248
248
|
timeout: 10000
|
|
249
249
|
});
|
|
250
250
|
|
|
251
|
-
|
|
252
|
-
// systems expose MiB; normalize to GB safely.
|
|
253
|
-
const gpuMemory = {};
|
|
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);
|
|
265
|
-
}
|
|
251
|
+
const gpuMemory = this.parseRocmSmiMemoryInfo(memInfo);
|
|
266
252
|
|
|
267
253
|
// Get temperature and utilization
|
|
268
254
|
let temps = {};
|
|
@@ -293,16 +279,28 @@ class ROCmDetector {
|
|
|
293
279
|
const numGPUs = Math.max(gpuNames.length, Object.keys(gpuMemory).length);
|
|
294
280
|
for (let i = 0; i < numGPUs; i++) {
|
|
295
281
|
const name = gpuNames[i] || `AMD GPU ${i}`;
|
|
296
|
-
const
|
|
282
|
+
const detectedVram = gpuMemory[i];
|
|
283
|
+
const memoryProfile = this.resolveGpuMemoryProfile(name, detectedVram);
|
|
284
|
+
const vram = memoryProfile.total;
|
|
285
|
+
|
|
286
|
+
if (!Number.isFinite(vram) || vram <= 0) {
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
297
289
|
|
|
298
290
|
const gpu = {
|
|
299
291
|
index: i,
|
|
300
292
|
name: name,
|
|
293
|
+
type: memoryProfile.type,
|
|
301
294
|
memory: {
|
|
302
295
|
total: vram,
|
|
303
296
|
free: vram,
|
|
304
|
-
used: 0
|
|
297
|
+
used: 0,
|
|
298
|
+
dedicated: memoryProfile.dedicated,
|
|
299
|
+
shared: memoryProfile.shared
|
|
305
300
|
},
|
|
301
|
+
dedicatedMemory: memoryProfile.dedicated,
|
|
302
|
+
sharedMemory: memoryProfile.shared,
|
|
303
|
+
unifiedMemory: memoryProfile.type === 'integrated' ? memoryProfile.shared : 0,
|
|
306
304
|
temperature: temps[i] || 0,
|
|
307
305
|
utilization: utils[i] || 0,
|
|
308
306
|
capabilities: this.getGPUCapabilities(name),
|
|
@@ -310,7 +308,8 @@ class ROCmDetector {
|
|
|
310
308
|
};
|
|
311
309
|
|
|
312
310
|
result.gpus.push(gpu);
|
|
313
|
-
result.totalVRAM += vram;
|
|
311
|
+
result.totalVRAM += memoryProfile.type === 'integrated' ? memoryProfile.dedicated : vram;
|
|
312
|
+
result.totalSharedMemory += memoryProfile.type === 'integrated' ? memoryProfile.shared : 0;
|
|
314
313
|
}
|
|
315
314
|
|
|
316
315
|
return result.gpus.length > 0;
|
|
@@ -319,6 +318,92 @@ class ROCmDetector {
|
|
|
319
318
|
}
|
|
320
319
|
}
|
|
321
320
|
|
|
321
|
+
parseRocmSmiProductNames(productOutput) {
|
|
322
|
+
const names = [];
|
|
323
|
+
const gfxFallbacks = {};
|
|
324
|
+
const deviceFallbacks = {};
|
|
325
|
+
const lines = String(productOutput || '').split('\n');
|
|
326
|
+
|
|
327
|
+
for (const line of lines) {
|
|
328
|
+
let match = line.match(/GPU\[(\d+)\]\s*:\s*Card Series\s*:\s*(.+)$/i);
|
|
329
|
+
if (match) {
|
|
330
|
+
names[parseInt(match[1], 10)] = this.normalizeRocmGpuName(match[2]);
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
match = line.match(/GPU\[(\d+)\]\s*:\s*Card Model\s*:\s*(?:0x)?([0-9a-f]{4})/i);
|
|
335
|
+
if (match) {
|
|
336
|
+
const deviceInfo = ROCmDetector.AMD_DEVICE_IDS[String(match[2]).toLowerCase()];
|
|
337
|
+
if (deviceInfo?.name) {
|
|
338
|
+
deviceFallbacks[parseInt(match[1], 10)] = deviceInfo.name;
|
|
339
|
+
}
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
match = line.match(/GPU\[(\d+)\]\s*:\s*GFX Version\s*:\s*(gfx\d+)/i);
|
|
344
|
+
if (match) {
|
|
345
|
+
gfxFallbacks[parseInt(match[1], 10)] = match[2].toLowerCase();
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const maxIndex = Math.max(
|
|
350
|
+
names.length - 1,
|
|
351
|
+
...Object.keys(gfxFallbacks).map(Number),
|
|
352
|
+
...Object.keys(deviceFallbacks).map(Number),
|
|
353
|
+
-1
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
for (let index = 0; index <= maxIndex; index += 1) {
|
|
357
|
+
if (!names[index]) {
|
|
358
|
+
names[index] = deviceFallbacks[index] || this.resolveGfxDisplayName(gfxFallbacks[index]);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return names;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
normalizeRocmGpuName(value) {
|
|
366
|
+
return String(value || '')
|
|
367
|
+
.replace(/^GFX Version\s*:\s*/i, '')
|
|
368
|
+
.replace(/\s+/g, ' ')
|
|
369
|
+
.trim();
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
resolveGfxDisplayName(gfxVersion) {
|
|
373
|
+
const gfx = String(gfxVersion || '').toLowerCase().trim();
|
|
374
|
+
if (!gfx) return null;
|
|
375
|
+
|
|
376
|
+
if (gfx === 'gfx1151') return 'AMD Radeon 8060S (gfx1151)';
|
|
377
|
+
if (gfx === 'gfx1150' || gfx === 'gfx1152') return `AMD Strix Halo GPU (${gfx})`;
|
|
378
|
+
if (gfx === 'gfx1103') return 'AMD Radeon 780M (gfx1103)';
|
|
379
|
+
if (gfx === 'gfx1200' || gfx === 'gfx1201') return `AMD RDNA 4 GPU (${gfx})`;
|
|
380
|
+
|
|
381
|
+
return gfx;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
parseRocmSmiMemoryInfo(memInfo) {
|
|
385
|
+
const gpuMemory = {};
|
|
386
|
+
const memLines = String(memInfo || '').split('\n');
|
|
387
|
+
|
|
388
|
+
for (const line of memLines) {
|
|
389
|
+
if (/Total\s+Used\s+Memory/i.test(line)) continue;
|
|
390
|
+
|
|
391
|
+
const lineMatch =
|
|
392
|
+
line.match(/GPU\[(\d+)\]\s*:\s*VRAM\s+Total\s+Memory\s*(?:\(([^)]+)\))?\s*:\s*(\d+)/i) ||
|
|
393
|
+
line.match(/GPU\[(\d+)\].*?\bTotal\s+Memory\s*(?:\(([^)]+)\))?\s*:\s*(\d+)/i);
|
|
394
|
+
if (!lineMatch) continue;
|
|
395
|
+
|
|
396
|
+
const idx = parseInt(lineMatch[1], 10);
|
|
397
|
+
const unitHint = lineMatch[2] || '';
|
|
398
|
+
const rawValue = parseInt(lineMatch[3], 10);
|
|
399
|
+
|
|
400
|
+
if (!Number.isFinite(rawValue) || rawValue <= 0) continue;
|
|
401
|
+
gpuMemory[idx] = this.normalizeRocmMemoryToGB(rawValue, unitHint);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return gpuMemory;
|
|
405
|
+
}
|
|
406
|
+
|
|
322
407
|
/**
|
|
323
408
|
* Normalize rocm-smi memory values to GB.
|
|
324
409
|
* rocm-smi may report bytes "(B)" or MiB depending on version/system.
|
|
@@ -358,6 +443,286 @@ class ROCmDetector {
|
|
|
358
443
|
return Math.round(numericValue);
|
|
359
444
|
}
|
|
360
445
|
|
|
446
|
+
/**
|
|
447
|
+
* Parse rocminfo output into deduplicated GPU agents.
|
|
448
|
+
*/
|
|
449
|
+
parseRocmInfoGpuAgents(rocmInfoOutput) {
|
|
450
|
+
const text = String(rocmInfoOutput || '');
|
|
451
|
+
if (!text.trim()) {
|
|
452
|
+
return [];
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const lines = text.split('\n');
|
|
456
|
+
const agents = [];
|
|
457
|
+
let current = null;
|
|
458
|
+
|
|
459
|
+
const pushCurrent = () => {
|
|
460
|
+
if (current) {
|
|
461
|
+
agents.push(current);
|
|
462
|
+
current = null;
|
|
463
|
+
}
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
for (const rawLine of lines) {
|
|
467
|
+
const line = String(rawLine || '');
|
|
468
|
+
|
|
469
|
+
const agentMatch = line.match(/^\s*Agent\s+(\d+)\s*:?\s*$/i);
|
|
470
|
+
if (agentMatch) {
|
|
471
|
+
pushCurrent();
|
|
472
|
+
current = { index: parseInt(agentMatch[1], 10) };
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (!current) continue;
|
|
477
|
+
|
|
478
|
+
let match = line.match(/^\s*Name:\s*(.+)$/i);
|
|
479
|
+
if (match) {
|
|
480
|
+
const value = match[1].trim();
|
|
481
|
+
if (!current.name) {
|
|
482
|
+
current.name = value;
|
|
483
|
+
} else {
|
|
484
|
+
if (!Array.isArray(current.aliases)) {
|
|
485
|
+
current.aliases = [];
|
|
486
|
+
}
|
|
487
|
+
current.aliases.push(value);
|
|
488
|
+
}
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
match = line.match(/^\s*Marketing Name:\s*(.+)$/i);
|
|
493
|
+
if (match) {
|
|
494
|
+
current.marketingName = match[1].trim();
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
match = line.match(/^\s*Device Type:\s*(.+)$/i);
|
|
499
|
+
if (match) {
|
|
500
|
+
current.deviceType = match[1].trim().toLowerCase();
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
match = line.match(/^\s*Uuid:\s*(.+)$/i);
|
|
505
|
+
if (match) {
|
|
506
|
+
current.uuid = match[1].trim();
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
pushCurrent();
|
|
511
|
+
|
|
512
|
+
const deduped = new Map();
|
|
513
|
+
for (const agent of agents) {
|
|
514
|
+
if (!this.isRocmGpuAgent(agent)) continue;
|
|
515
|
+
|
|
516
|
+
const displayName = this.getRocmAgentDisplayName(agent);
|
|
517
|
+
const key = this.getRocmAgentKey(agent, displayName);
|
|
518
|
+
if (!deduped.has(key)) {
|
|
519
|
+
deduped.set(key, { name: displayName, source: agent });
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
return Array.from(deduped.values());
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
isRocmGpuAgent(agent = {}) {
|
|
527
|
+
const deviceType = String(agent.deviceType || '').toLowerCase();
|
|
528
|
+
if (deviceType && deviceType !== 'gpu') {
|
|
529
|
+
return false;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const probe = [
|
|
533
|
+
agent.marketingName,
|
|
534
|
+
agent.name,
|
|
535
|
+
...(Array.isArray(agent.aliases) ? agent.aliases : [])
|
|
536
|
+
].join(' ').toLowerCase();
|
|
537
|
+
|
|
538
|
+
if (!probe) return false;
|
|
539
|
+
if (probe.includes('cpu')) return false;
|
|
540
|
+
|
|
541
|
+
return /(gfx\d{3,}|amd|radeon|instinct|rx\s*\d{3,4}|mi\d{3,4})/i.test(probe);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
getRocmAgentDisplayName(agent = {}) {
|
|
545
|
+
const candidates = [
|
|
546
|
+
agent.marketingName,
|
|
547
|
+
agent.name,
|
|
548
|
+
...(Array.isArray(agent.aliases) ? agent.aliases : [])
|
|
549
|
+
]
|
|
550
|
+
.map((item) => String(item || '').trim())
|
|
551
|
+
.filter(Boolean)
|
|
552
|
+
.filter((item) => item.toLowerCase() !== 'n/a');
|
|
553
|
+
|
|
554
|
+
const descriptive = candidates.find((item) => !this.isGenericRocmName(item) && !/^gfx\d+$/i.test(item));
|
|
555
|
+
if (descriptive) return descriptive;
|
|
556
|
+
|
|
557
|
+
const gfx = candidates.find((item) => /^gfx\d+$/i.test(item) || /--gfx\d+/i.test(item));
|
|
558
|
+
if (gfx) return this.resolveGfxDisplayName((gfx.match(/gfx\d+/i) || [gfx])[0]) || gfx;
|
|
559
|
+
|
|
560
|
+
const fallback = candidates.find((item) => !this.isGenericRocmName(item));
|
|
561
|
+
if (fallback) return fallback;
|
|
562
|
+
|
|
563
|
+
return 'AMD GPU';
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
getRocmAgentKey(agent = {}, resolvedName = '') {
|
|
567
|
+
const uuid = String(agent.uuid || '').trim().toLowerCase();
|
|
568
|
+
if (uuid) {
|
|
569
|
+
return `uuid:${uuid}`;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
const probe = [
|
|
573
|
+
resolvedName,
|
|
574
|
+
agent.marketingName,
|
|
575
|
+
agent.name,
|
|
576
|
+
...(Array.isArray(agent.aliases) ? agent.aliases : [])
|
|
577
|
+
].join(' ').toLowerCase();
|
|
578
|
+
const gfxMatch = probe.match(/\bgfx\d{3,4}\b/);
|
|
579
|
+
if (gfxMatch) {
|
|
580
|
+
return `gfx:${gfxMatch[0]}`;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
const normalizedName = String(resolvedName || '')
|
|
584
|
+
.toLowerCase()
|
|
585
|
+
.replace(/\s+/g, ' ')
|
|
586
|
+
.trim();
|
|
587
|
+
|
|
588
|
+
if (normalizedName) {
|
|
589
|
+
return `name:${normalizedName}`;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
return `agent:${agent.index || 0}`;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
isGenericRocmName(name = '') {
|
|
596
|
+
const lower = String(name || '').toLowerCase().replace(/\s+/g, ' ').trim();
|
|
597
|
+
return lower === 'amd' ||
|
|
598
|
+
lower === 'advanced micro devices' ||
|
|
599
|
+
lower === 'advanced micro devices, inc.' ||
|
|
600
|
+
lower === 'advanced micro devices, inc. [amd/ati]' ||
|
|
601
|
+
lower.startsWith('amdgcn-amd-amdhsa--');
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
isLikelyIntegratedGPU(name = '') {
|
|
605
|
+
const nameLower = String(name || '').toLowerCase();
|
|
606
|
+
if (!nameLower) return false;
|
|
607
|
+
|
|
608
|
+
if (nameLower.includes('integrated') || nameLower.includes('apu')) return true;
|
|
609
|
+
if (nameLower.includes('radeon graphics') && !nameLower.includes('rx')) return true;
|
|
610
|
+
if (nameLower.includes('radeon 8060s') || nameLower.includes('radeon 890m') ||
|
|
611
|
+
nameLower.includes('radeon 880m') || nameLower.includes('radeon 780m') ||
|
|
612
|
+
nameLower.includes('radeon 680m')) return true;
|
|
613
|
+
if (nameLower.includes('gfx1150') || nameLower.includes('gfx1151') || nameLower.includes('gfx1152')) return true;
|
|
614
|
+
if (nameLower.includes('gfx1103') || nameLower.includes('gfx1035')) return true;
|
|
615
|
+
|
|
616
|
+
return false;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
applyIntegratedVramHeuristic(name, vramGB) {
|
|
620
|
+
const numericVram = Number(vramGB);
|
|
621
|
+
if (!Number.isFinite(numericVram) || numericVram <= 0) {
|
|
622
|
+
return numericVram;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
if (!this.isLikelyIntegratedGPU(name)) {
|
|
626
|
+
return numericVram;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
if (numericVram >= 4) {
|
|
630
|
+
return numericVram;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
const estimated = this.estimateVRAMFromModel(name);
|
|
634
|
+
if (Number.isFinite(estimated) && estimated > numericVram) {
|
|
635
|
+
return estimated;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
return 8;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
resolveGpuMemoryProfile(name, detectedVramGB) {
|
|
642
|
+
const detected = Number(detectedVramGB);
|
|
643
|
+
const detectedVram = Number.isFinite(detected) && detected > 0 ? detected : 0;
|
|
644
|
+
|
|
645
|
+
if (!this.isLikelyIntegratedGPU(name)) {
|
|
646
|
+
const total = detectedVram || this.estimateVRAMFromModel(name) || 8;
|
|
647
|
+
return {
|
|
648
|
+
type: 'dedicated',
|
|
649
|
+
total,
|
|
650
|
+
dedicated: total,
|
|
651
|
+
shared: 0
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
const sysfsProfile = this.getIntegratedMemoryProfile();
|
|
656
|
+
const estimatedShared = this.applyIntegratedVramHeuristic(
|
|
657
|
+
name,
|
|
658
|
+
detectedVram || this.estimateVRAMFromModel(name)
|
|
659
|
+
);
|
|
660
|
+
const dedicated = sysfsProfile.dedicated || detectedVram || 0;
|
|
661
|
+
const shared = Math.max(sysfsProfile.shared || 0, estimatedShared || 0, dedicated);
|
|
662
|
+
const total = shared || dedicated || this.estimateVRAMFromModel(name) || 8;
|
|
663
|
+
|
|
664
|
+
return {
|
|
665
|
+
type: 'integrated',
|
|
666
|
+
total,
|
|
667
|
+
dedicated,
|
|
668
|
+
shared: total
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
getIntegratedMemoryProfile() {
|
|
673
|
+
const dedicatedValues = this.readAmdSysfsMemoryValues('mem_info_vram_total');
|
|
674
|
+
const sharedValues = this.readAmdSysfsMemoryValues('mem_info_gtt_total');
|
|
675
|
+
const totalSystemGB = Math.max(1, Math.round(os.totalmem() / (1024 ** 3)));
|
|
676
|
+
const rawShared = sharedValues.length > 0 ? Math.max(...sharedValues) : 0;
|
|
677
|
+
|
|
678
|
+
return {
|
|
679
|
+
dedicated: dedicatedValues.length > 0 ? Math.max(...dedicatedValues) : 0,
|
|
680
|
+
shared: rawShared > 0 ? Math.min(rawShared, Math.round(totalSystemGB * 0.95)) : 0
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
readAmdSysfsMemoryValues(fileName) {
|
|
685
|
+
const values = [];
|
|
686
|
+
const candidatePaths = [];
|
|
687
|
+
|
|
688
|
+
try {
|
|
689
|
+
const moduleRoot = '/sys/module/amdgpu/drivers/pci:amdgpu';
|
|
690
|
+
for (const entry of fs.readdirSync(moduleRoot)) {
|
|
691
|
+
candidatePaths.push(path.join(moduleRoot, entry, fileName));
|
|
692
|
+
}
|
|
693
|
+
} catch (e) {
|
|
694
|
+
// sysfs may be unavailable or restricted
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
try {
|
|
698
|
+
const drmRoot = '/sys/class/drm';
|
|
699
|
+
for (const entry of fs.readdirSync(drmRoot)) {
|
|
700
|
+
if (!/^card\d+$/.test(entry)) continue;
|
|
701
|
+
candidatePaths.push(path.join(drmRoot, entry, 'device', fileName));
|
|
702
|
+
}
|
|
703
|
+
} catch (e) {
|
|
704
|
+
// sysfs may be unavailable or restricted
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
const seen = new Set();
|
|
708
|
+
for (const candidatePath of candidatePaths) {
|
|
709
|
+
if (seen.has(candidatePath)) continue;
|
|
710
|
+
seen.add(candidatePath);
|
|
711
|
+
|
|
712
|
+
try {
|
|
713
|
+
const raw = parseInt(fs.readFileSync(candidatePath, 'utf8').trim(), 10);
|
|
714
|
+
const gb = this.normalizeRocmMemoryToGB(raw, 'B');
|
|
715
|
+
if (Number.isFinite(gb) && gb > 0) {
|
|
716
|
+
values.push(gb);
|
|
717
|
+
}
|
|
718
|
+
} catch (e) {
|
|
719
|
+
continue;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
return values;
|
|
724
|
+
}
|
|
725
|
+
|
|
361
726
|
/**
|
|
362
727
|
* Detect GPUs via rocminfo
|
|
363
728
|
*/
|
|
@@ -368,23 +733,36 @@ class ROCmDetector {
|
|
|
368
733
|
timeout: 10000
|
|
369
734
|
});
|
|
370
735
|
|
|
371
|
-
const
|
|
372
|
-
let
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
736
|
+
const agents = this.parseRocmInfoGpuAgents(rocmInfo);
|
|
737
|
+
for (let index = 0; index < agents.length; index += 1) {
|
|
738
|
+
const name = agents[index].name;
|
|
739
|
+
let vram = this.estimateVRAMFromGfxName(name);
|
|
740
|
+
const memoryProfile = this.resolveGpuMemoryProfile(name, vram);
|
|
741
|
+
vram = memoryProfile.total;
|
|
377
742
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
name: name,
|
|
381
|
-
memory: { total: vram, free: vram, used: 0 },
|
|
382
|
-
capabilities: this.getGPUCapabilities(name),
|
|
383
|
-
speedCoefficient: this.calculateSpeedCoefficient(name, vram)
|
|
384
|
-
});
|
|
385
|
-
result.totalVRAM += vram;
|
|
386
|
-
idx++;
|
|
743
|
+
if (!Number.isFinite(vram) || vram <= 0) {
|
|
744
|
+
vram = 8;
|
|
387
745
|
}
|
|
746
|
+
|
|
747
|
+
result.gpus.push({
|
|
748
|
+
index,
|
|
749
|
+
name,
|
|
750
|
+
type: memoryProfile.type,
|
|
751
|
+
memory: {
|
|
752
|
+
total: vram,
|
|
753
|
+
free: vram,
|
|
754
|
+
used: 0,
|
|
755
|
+
dedicated: memoryProfile.dedicated,
|
|
756
|
+
shared: memoryProfile.shared
|
|
757
|
+
},
|
|
758
|
+
dedicatedMemory: memoryProfile.dedicated,
|
|
759
|
+
sharedMemory: memoryProfile.shared,
|
|
760
|
+
unifiedMemory: memoryProfile.type === 'integrated' ? memoryProfile.shared : 0,
|
|
761
|
+
capabilities: this.getGPUCapabilities(name),
|
|
762
|
+
speedCoefficient: this.calculateSpeedCoefficient(name, vram)
|
|
763
|
+
});
|
|
764
|
+
result.totalVRAM += memoryProfile.type === 'integrated' ? memoryProfile.dedicated : vram;
|
|
765
|
+
result.totalSharedMemory += memoryProfile.type === 'integrated' ? memoryProfile.shared : 0;
|
|
388
766
|
}
|
|
389
767
|
|
|
390
768
|
return result.gpus.length > 0;
|
|
@@ -476,7 +854,7 @@ class ROCmDetector {
|
|
|
476
854
|
|
|
477
855
|
const deviceInfo = ROCmDetector.AMD_DEVICE_IDS[deviceId];
|
|
478
856
|
const name = deviceInfo?.name || `AMD GPU (${deviceId})`;
|
|
479
|
-
let vram = deviceInfo?.vram ||
|
|
857
|
+
let vram = deviceInfo?.vram || this.estimateVRAMFromModel(name);
|
|
480
858
|
|
|
481
859
|
// Try to read VRAM from sysfs
|
|
482
860
|
const vramPaths = [
|
|
@@ -596,6 +974,7 @@ class ROCmDetector {
|
|
|
596
974
|
|
|
597
975
|
// RDNA 4 / Radeon AI PRO
|
|
598
976
|
if (nameLower.includes('r9700') || nameLower.includes('ai pro') ||
|
|
977
|
+
nameLower.includes('rx 9070') || nameLower.includes('rx 9060') ||
|
|
599
978
|
nameLower.includes('gfx1200') || nameLower.includes('gfx1201')) {
|
|
600
979
|
capabilities.bf16 = true;
|
|
601
980
|
capabilities.matrixCores = true;
|
|
@@ -655,7 +1034,19 @@ class ROCmDetector {
|
|
|
655
1034
|
estimateVRAMFromModel(name) {
|
|
656
1035
|
const nameLower = (name || '').toLowerCase();
|
|
657
1036
|
|
|
1037
|
+
// Known integrated/APU labels where ROCm can report small dedicated aperture
|
|
1038
|
+
if (nameLower.includes('gfx1151') || nameLower.includes('gfx1150') || nameLower.includes('gfx1152')) return 16;
|
|
1039
|
+
if (nameLower.includes('radeon 8060s')) return 16;
|
|
1040
|
+
if (nameLower.includes('radeon 890m')) return 16;
|
|
1041
|
+
if (nameLower.includes('radeon 880m')) return 12;
|
|
1042
|
+
if (nameLower.includes('radeon 780m')) return 8;
|
|
1043
|
+
if (nameLower.includes('radeon 680m')) return 8;
|
|
1044
|
+
|
|
658
1045
|
// RDNA 4 / Radeon AI PRO
|
|
1046
|
+
if (nameLower.includes('rx 9070 xt')) return 16;
|
|
1047
|
+
if (nameLower.includes('rx 9070')) return 16;
|
|
1048
|
+
if (nameLower.includes('rx 9060 xt')) return 16;
|
|
1049
|
+
if (nameLower.includes('rx 9060')) return 8;
|
|
659
1050
|
if (nameLower.includes('r9700') || nameLower.includes('ai pro r9700')) return 32;
|
|
660
1051
|
|
|
661
1052
|
// RX 7000 series
|
|
@@ -694,10 +1085,12 @@ class ROCmDetector {
|
|
|
694
1085
|
estimateVRAMFromGfxName(name) {
|
|
695
1086
|
const nameLower = (name || '').toLowerCase();
|
|
696
1087
|
|
|
697
|
-
if (nameLower.includes('gfx1200') || nameLower.includes('gfx1201')) return
|
|
1088
|
+
if (nameLower.includes('gfx1200') || nameLower.includes('gfx1201')) return 16; // RDNA 4 desktop default
|
|
1089
|
+
if (nameLower.includes('gfx1150') || nameLower.includes('gfx1151') || nameLower.includes('gfx1152')) return 16; // Strix Halo / Radeon 890M class
|
|
698
1090
|
if (nameLower.includes('gfx1100')) return 24; // RX 7900 XTX
|
|
699
1091
|
if (nameLower.includes('gfx1101')) return 16; // RX 7800
|
|
700
1092
|
if (nameLower.includes('gfx1102')) return 8; // RX 7600
|
|
1093
|
+
if (nameLower.includes('gfx1103')) return 8; // 780M class iGPU
|
|
701
1094
|
if (nameLower.includes('gfx1030')) return 16; // RX 6900/6800
|
|
702
1095
|
if (nameLower.includes('gfx1031')) return 12; // RX 6700
|
|
703
1096
|
if (nameLower.includes('gfx1032')) return 8; // RX 6600
|
|
@@ -714,40 +1107,48 @@ class ROCmDetector {
|
|
|
714
1107
|
const nameLower = (name || '').toLowerCase();
|
|
715
1108
|
|
|
716
1109
|
// Speed coefficients (tokens/sec per B params at Q4)
|
|
717
|
-
const speedMap =
|
|
1110
|
+
const speedMap = new Map([
|
|
718
1111
|
// RDNA 4 / Radeon AI PRO
|
|
719
|
-
'r9700'
|
|
720
|
-
'
|
|
1112
|
+
['ai pro r9700', 230],
|
|
1113
|
+
['r9700', 230],
|
|
1114
|
+
['rx 9070 xt', 190],
|
|
1115
|
+
['rx 9070', 175],
|
|
1116
|
+
['rx 9060 xt', 170],
|
|
1117
|
+
['rx 9060', 150],
|
|
1118
|
+
['radeon 8060s', 160],
|
|
1119
|
+
['gfx1151', 160],
|
|
1120
|
+
['gfx1150', 150],
|
|
1121
|
+
['gfx1152', 150],
|
|
721
1122
|
|
|
722
1123
|
// RX 7000 series (RDNA 3)
|
|
723
|
-
'7900 xtx'
|
|
724
|
-
'7900 xt'
|
|
725
|
-
'7900 gre'
|
|
726
|
-
'7800 xt'
|
|
727
|
-
'7700 xt'
|
|
728
|
-
'7600'
|
|
1124
|
+
['7900 xtx', 200],
|
|
1125
|
+
['7900 xt', 180],
|
|
1126
|
+
['7900 gre', 160],
|
|
1127
|
+
['7800 xt', 150],
|
|
1128
|
+
['7700 xt', 120],
|
|
1129
|
+
['7600', 90],
|
|
729
1130
|
|
|
730
1131
|
// RX 6000 series (RDNA 2)
|
|
731
|
-
'6950 xt'
|
|
732
|
-
'6900 xt'
|
|
733
|
-
'6800 xt'
|
|
734
|
-
'6800'
|
|
735
|
-
'6750 xt'
|
|
736
|
-
'6700 xt'
|
|
737
|
-
'6700'
|
|
738
|
-
'6600 xt'
|
|
739
|
-
'6600'
|
|
1132
|
+
['6950 xt', 150],
|
|
1133
|
+
['6900 xt', 140],
|
|
1134
|
+
['6800 xt', 130],
|
|
1135
|
+
['6800', 120],
|
|
1136
|
+
['6750 xt', 100],
|
|
1137
|
+
['6700 xt', 90],
|
|
1138
|
+
['6700', 80],
|
|
1139
|
+
['6600 xt', 70],
|
|
1140
|
+
['6600', 60],
|
|
740
1141
|
|
|
741
1142
|
// Instinct series
|
|
742
|
-
'mi300x'
|
|
743
|
-
'mi300'
|
|
744
|
-
'mi250x'
|
|
745
|
-
'mi250'
|
|
746
|
-
'mi210'
|
|
747
|
-
'mi100'
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
for (const [model, speed] of
|
|
1143
|
+
['mi300x', 400],
|
|
1144
|
+
['mi300', 350],
|
|
1145
|
+
['mi250x', 280],
|
|
1146
|
+
['mi250', 250],
|
|
1147
|
+
['mi210', 200],
|
|
1148
|
+
['mi100', 150]
|
|
1149
|
+
]);
|
|
1150
|
+
|
|
1151
|
+
for (const [model, speed] of speedMap) {
|
|
751
1152
|
if (nameLower.includes(model)) {
|
|
752
1153
|
return speed;
|
|
753
1154
|
}
|