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.
@@ -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
- // Parse GPU names
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
- // Parse memory info. Newer rocm-smi reports bytes "(B)" while some
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 vram = gpuMemory[i] || this.estimateVRAMFromModel(name);
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 agentMatches = rocmInfo.matchAll(/Name:\s*(gfx\d+|AMD.*)/gi);
372
- let idx = 0;
373
- for (const match of agentMatches) {
374
- const name = match[1].trim();
375
- if (name.toLowerCase().includes('gfx') || name.toLowerCase().includes('amd')) {
376
- const vram = this.estimateVRAMFromGfxName(name);
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
- result.gpus.push({
379
- index: idx,
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 || 8;
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 32; // Radeon AI PRO R9700
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': 230,
720
- 'ai pro r9700': 230,
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': 200,
724
- '7900 xt': 180,
725
- '7900 gre': 160,
726
- '7800 xt': 150,
727
- '7700 xt': 120,
728
- '7600': 90,
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': 150,
732
- '6900 xt': 140,
733
- '6800 xt': 130,
734
- '6800': 120,
735
- '6750 xt': 100,
736
- '6700 xt': 90,
737
- '6700': 80,
738
- '6600 xt': 70,
739
- '6600': 60,
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': 400,
743
- 'mi300': 350,
744
- 'mi250x': 280,
745
- 'mi250': 250,
746
- 'mi210': 200,
747
- 'mi100': 150
748
- };
749
-
750
- for (const [model, speed] of Object.entries(speedMap)) {
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
  }