Files
bare-operating-system/packages/bare-os-booter/test.qvac-gpu-probe.js
T
2026-08-18 18:11:34 -04:00

304 lines
9.1 KiB
JavaScript

import test from 'brittle'
import {
bareOsQvacParseVulkaninfo,
bareOsQvacParseNvidiaSmi,
bareOsQvacMergeNvidiaHints,
bareOsQvacRankGpus,
bareOsQvacMainGpuCandidates,
bareOsQvacProbeGpus
} from './lib/services/bare-os-qvac-gpu-probe.mjs'
const SAMPLE_VULKAN = `
GPU0:
apiVersion = 1.3.0
driverVersion = 1
vendorID = 0x8086
deviceID = 0x9a49
deviceType = PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU
deviceName = Intel(R) UHD Graphics (TGL GT1)
memoryHeaps: count = 1
memoryHeaps[0]:
size = 536870912 (0x20000000) (512.00 MiB)
flags: count = 1
MEMORY_HEAP_DEVICE_LOCAL_BIT
GPU1:
apiVersion = 1.3.0
driverVersion = 1
vendorID = 0x10de
deviceID = 0x25a0
deviceType = PHYSICAL_DEVICE_TYPE_DISCRETE_GPU
deviceName = NVIDIA GeForce RTX 3050 Ti Laptop GPU
memoryHeaps: count = 2
memoryHeaps[0]:
size = 4294967296 (0x100000000) (4096.00 MiB)
flags: count = 1
MEMORY_HEAP_DEVICE_LOCAL_BIT
memoryHeaps[1]:
size = 12582912 (0xc00000) (12.00 MiB)
flags: count = 0
`
test('parse vulkaninfo ranks NVIDIA above Intel by VRAM', async (t) => {
const gpus = bareOsQvacRankGpus(bareOsQvacParseVulkaninfo(SAMPLE_VULKAN))
t.is(gpus.length, 2)
t.is(gpus[0].index, 1)
t.ok(gpus[0].name.includes('NVIDIA'))
t.is(gpus[0].memoryBytes, 4294967296)
t.is(gpus[1].index, 0)
t.ok(gpus[1].memoryBytes < gpus[0].memoryBytes)
const cands = bareOsQvacMainGpuCandidates(gpus)
t.is(cands[0], 1)
t.ok(cands.includes('dedicated'))
})
test('nvidia-smi parse + candidates avoid trusting smi index as vulkan', async (t) => {
const n = bareOsQvacParseNvidiaSmi('NVIDIA GeForce RTX 3050 Ti Laptop GPU, 4096\n')
t.is(n.length, 1)
t.is(n[0].memoryBytes, 4096 * 1024 * 1024)
const synth = n.map((x, i) => ({
index: i,
name: x.name,
memoryBytes: x.memoryBytes,
deviceType: 'PHYSICAL_DEVICE_TYPE_DISCRETE_GPU',
source: 'nvidia-smi'
}))
const cands = bareOsQvacMainGpuCandidates(synth)
t.is(cands[0], 'dedicated')
t.is(cands[1], 1)
})
test('merge nvidia hints upgrades vulkan heap estimate', async (t) => {
const vulkan = bareOsQvacParseVulkaninfo(`
GPU0:
deviceType = PHYSICAL_DEVICE_TYPE_DISCRETE_GPU
deviceName = NVIDIA GeForce RTX 3050 Ti Laptop GPU
`)
t.is(vulkan[0].memoryBytes, 8 * 1024 * 1024 * 1024) // discrete heuristic
const merged = bareOsQvacMergeNvidiaHints(vulkan, [
{ name: 'NVIDIA GeForce RTX 3050 Ti Laptop GPU', memoryBytes: 4096 * 1024 * 1024 }
])
// hint is smaller than discrete heuristic — keep larger
t.is(merged[0].memoryBytes, 8 * 1024 * 1024 * 1024)
const mergedUp = bareOsQvacMergeNvidiaHints(
[{ ...vulkan[0], memoryBytes: 512 * 1024 * 1024 }],
[{ name: 'NVIDIA GeForce RTX 3050 Ti Laptop GPU', memoryBytes: 4096 * 1024 * 1024 }]
)
t.is(mergedUp[0].memoryBytes, 4096 * 1024 * 1024)
})
test('probe with injected runCommand', async (t) => {
const runCommand = async (file, args) => {
if (file === 'system_profiler') {
return JSON.stringify({
SPDisplaysDataType: [{ _name: 'Apple M2 Pro' }]
})
}
if (file === 'vulkaninfo') return SAMPLE_VULKAN
if (file === 'nvidia-smi') return 'NVIDIA GeForce RTX 3050 Ti Laptop GPU, 4096\n'
return null
}
const ranked = await bareOsQvacProbeGpus({ runCommand })
if (ranked[0] && ranked[0].source === 'metal') {
// macOS probe path (system_profiler → ggml-metal).
t.is(ranked.length, 1)
t.is(ranked[0].index, 0)
t.ok(ranked[0].name.includes('Apple'))
} else {
t.is(ranked[0].index, 1)
t.ok(String(ranked[0].source).includes('vulkaninfo'))
}
})
test('parse system_profiler displays JSON → metal GPU', async (t) => {
const { bareOsQvacParseSystemProfilerDisplays, bareOsQvacParseMetalMemory } =
await import('./lib/services/bare-os-qvac-gpu-probe.mjs')
const gpus = bareOsQvacParseSystemProfilerDisplays(
JSON.stringify({
SPDisplaysDataType: [
{
_name: 'Apple M2 Pro',
spdisplays_metal: 'spdisplays_metal4',
spdisplays_vram_shared: '18 GB'
}
]
})
)
t.is(gpus.length, 1)
t.is(gpus[0].index, 0)
t.ok(gpus[0].name.includes('Apple'))
t.is(gpus[0].memoryBytes, 18 * 1024 * 1024 * 1024)
t.is(gpus[0].source, 'metal')
// Same GPU listed once per attached display must dedupe.
const dupes = bareOsQvacParseSystemProfilerDisplays(
JSON.stringify({
SPDisplaysDataType: [{ _name: 'Apple M2 Pro' }, { _name: 'Apple M2 Pro' }]
})
)
t.is(dupes.length, 1)
t.is(
bareOsQvacParseMetalMemory('Shared system memory: 18 GB'),
18 * 1024 * 1024 * 1024
)
t.is(bareOsQvacParseMetalMemory(''), 0)
})
test('software Vulkan devices are detected and ranked last', async (t) => {
const {
bareOsQvacIsSoftwareVulkanDevice,
bareOsQvacHardwareGpus,
bareOsQvacScoreVulkanIcdFilename,
bareOsQvacDiscoverHardwareVulkanIcds,
bareOsQvacApplyHardwareVulkanEnv
} = await import('./lib/services/bare-os-qvac-gpu-probe.mjs')
t.ok(
bareOsQvacIsSoftwareVulkanDevice({
name: 'llvmpipe (LLVM 22.1.8, 256 bits)',
deviceType: 'PHYSICAL_DEVICE_TYPE_CPU'
})
)
t.absent(
bareOsQvacIsSoftwareVulkanDevice({
name: 'NVIDIA GeForce RTX 3050 Ti',
deviceType: 'PHYSICAL_DEVICE_TYPE_DISCRETE_GPU'
})
)
const mixed = bareOsQvacRankGpus([
{
index: 0,
name: 'llvmpipe',
memoryBytes: 64 * 1024 * 1024 * 1024,
deviceType: 'PHYSICAL_DEVICE_TYPE_CPU',
source: 'vulkaninfo'
},
{
index: 1,
name: 'NVIDIA GeForce RTX 3050 Ti',
memoryBytes: 4 * 1024 * 1024 * 1024,
deviceType: 'PHYSICAL_DEVICE_TYPE_DISCRETE_GPU',
source: 'vulkaninfo'
}
])
t.is(mixed[0].index, 1)
t.is(bareOsQvacHardwareGpus(mixed).length, 1)
t.ok(bareOsQvacScoreVulkanIcdFilename('nvidia_icd.json') > 0)
t.ok(bareOsQvacScoreVulkanIcdFilename('lvp_icd.x86_64.json') < 0)
const icds = bareOsQvacDiscoverHardwareVulkanIcds({
dirs: ['/fake/icd'],
existsSync: () => true,
readdirSync: () => [
'lvp_icd.x86_64.json',
'nvidia_icd.json',
'intel_icd.x86_64.json'
]
})
t.alike(icds, [
'/fake/icd/nvidia_icd.json',
'/fake/icd/intel_icd.x86_64.json'
])
/** @type {Record<string, string>} */
const procEnv = {}
const applied = bareOsQvacApplyHardwareVulkanEnv(
{},
{
procEnv,
icds: ['/usr/share/vulkan/icd.d/nvidia_icd.json'],
hasNvidiaSmi: true
}
)
t.ok(applied.preferNvidia)
t.ok(String(procEnv.VK_LOADER_DRIVERS_DISABLE).includes('lvp'))
t.is(procEnv.VK_LOADER_DRIVERS_SELECT, '*nvidia*')
t.is(procEnv.__NV_PRIME_RENDER_OFFLOAD, '1')
t.ok(String(procEnv.VK_DRIVER_FILES).includes('nvidia_icd.json'))
})
test('AMD RADV is preferred over unused Mesa ICDs', async (t) => {
const {
bareOsQvacScoreVulkanIcdFilename,
bareOsQvacVulkanIcdVendor,
bareOsQvacPreferredVulkanIcds,
bareOsQvacDiscoverHardwareVulkanIcds,
bareOsQvacApplyHardwareVulkanEnv
} = await import('./lib/services/bare-os-qvac-gpu-probe.mjs')
t.is(bareOsQvacVulkanIcdVendor('radeon_icd.json'), 'amd')
t.is(bareOsQvacVulkanIcdVendor('gfxstream_vk_icd.json'), 'virtual')
t.ok(
bareOsQvacScoreVulkanIcdFilename('radeon_icd.json') >
bareOsQvacScoreVulkanIcdFilename('intel_icd.json')
)
t.ok(bareOsQvacScoreVulkanIcdFilename('gfxstream_vk_icd.json') < 0)
const icds = bareOsQvacDiscoverHardwareVulkanIcds({
dirs: ['/fake/icd'],
existsSync: () => true,
readdirSync: () => [
'radeon_icd.json',
'intel_hasvk_icd.json',
'intel_icd.json',
'asahi_icd.json',
'gfxstream_vk_icd.json',
'nouveau_icd.json',
'virtio_icd.json',
'lvp_icd.json'
]
})
t.ok(icds[0].endsWith('radeon_icd.json'))
t.absent(icds.some((p) => /gfxstream|virtio|lvp/.test(p)))
const preferred = bareOsQvacPreferredVulkanIcds(icds)
t.is(preferred.vendor, 'amd')
t.alike(preferred.icds, ['/fake/icd/radeon_icd.json'])
t.is(preferred.selectGlob, '*radeon*,*amd*,*radv*')
/** @type {Record<string, string>} */
const procEnv = {}
const applied = bareOsQvacApplyHardwareVulkanEnv(
{},
{
procEnv,
icds
}
)
t.ok(applied.preferAmd)
t.absent(applied.preferNvidia)
t.is(applied.vendor, 'amd')
t.is(procEnv.VK_DRIVER_FILES, '/fake/icd/radeon_icd.json')
t.is(procEnv.VK_LOADER_DRIVERS_SELECT, '*radeon*,*amd*,*radv*')
t.ok(applied.note.includes('RADV/AMD'))
})
test('nvidia-smi without NVIDIA ICD keeps hardware list and SELECT', async (t) => {
const { bareOsQvacApplyHardwareVulkanEnv } = await import(
'./lib/services/bare-os-qvac-gpu-probe.mjs'
)
/** @type {Record<string, string>} */
const procEnv = {}
const applied = bareOsQvacApplyHardwareVulkanEnv(
{},
{
procEnv,
hasNvidiaSmi: true,
icds: [
'/fake/icd/intel_icd.json',
'/fake/icd/intel_hasvk_icd.json',
'/fake/icd/asahi_icd.json'
]
}
)
t.ok(applied.preferNvidia)
t.absent(applied.preferAmd)
t.is(applied.vendor, 'nvidia')
t.ok(String(procEnv.VK_DRIVER_FILES).includes('intel_icd.json'))
t.is(procEnv.VK_LOADER_DRIVERS_SELECT, '*nvidia*')
})