fix(cli): make status device probe opt-in

This commit is contained in:
Tobias Lütke 2026-04-21 21:44:15 -04:00
parent cfd640ed34
commit e8de7cab02
3 changed files with 40 additions and 26 deletions

View File

@ -9,6 +9,9 @@
`.toLowerCase()` call made indexed paths unreachable on case-sensitive
filesystems (Linux). `qmd update` automatically migrates legacy
lowercase paths without re-embedding.
- CLI: make `qmd status` skip native `node-llama-cpp` device probing by
default so status stays safe on machines with broken or unsupported GPU
drivers. Set `QMD_STATUS_DEVICE_PROBE=1` to opt in.
## [2.1.0] - 2026-04-05

View File

@ -468,35 +468,40 @@ async function showStatus(): Promise<void> {
}
// Device / GPU info
console.log(`\n${c.bold}Device${c.reset}`);
try {
const llm = getDefaultLlamaCpp();
const device = await llm.getDeviceInfo({ allowBuild: false });
if (device.gpu) {
console.log(` GPU: ${c.green}${device.gpu}${c.reset} (offloading: ${device.gpuOffloading ? 'yes' : 'no'})`);
if (device.gpuDevices.length > 0) {
// Deduplicate and count GPUs
const counts = new Map<string, number>();
for (const name of device.gpuDevices) {
counts.set(name, (counts.get(name) || 0) + 1);
// Important: probing node-llama-cpp can abort the whole process on machines with
// incompatible GPU drivers (for example Vulkan loader present but no usable driver).
// Keep `qmd status` safe by default and make the expensive/native probe opt-in.
if (process.env.QMD_STATUS_DEVICE_PROBE === "1") {
console.log(`\n${c.bold}Device${c.reset}`);
try {
const llm = getDefaultLlamaCpp();
const device = await llm.getDeviceInfo({ allowBuild: false });
if (device.gpu) {
console.log(` GPU: ${c.green}${device.gpu}${c.reset} (offloading: ${device.gpuOffloading ? 'yes' : 'no'})`);
if (device.gpuDevices.length > 0) {
// Deduplicate and count GPUs
const counts = new Map<string, number>();
for (const name of device.gpuDevices) {
counts.set(name, (counts.get(name) || 0) + 1);
}
const deviceStr = Array.from(counts.entries())
.map(([name, count]) => count > 1 ? `${count}× ${name}` : name)
.join(', ');
console.log(` Devices: ${deviceStr}`);
}
const deviceStr = Array.from(counts.entries())
.map(([name, count]) => count > 1 ? `${count}× ${name}` : name)
.join(', ');
console.log(` Devices: ${deviceStr}`);
if (device.vram) {
console.log(` VRAM: ${formatBytes(device.vram.free)} free / ${formatBytes(device.vram.total)} total`);
}
} else {
console.log(` GPU: ${c.yellow}none${c.reset} (running on CPU — models will be slow)`);
console.log(` ${c.dim}Tip: Install CUDA, Vulkan, or Metal support for GPU acceleration.${c.reset}`);
}
if (device.vram) {
console.log(` VRAM: ${formatBytes(device.vram.free)} free / ${formatBytes(device.vram.total)} total`);
console.log(` CPU: ${device.cpuCores} math cores`);
} catch (error) {
console.log(` Status: ${c.dim}probe failed${c.reset}`);
if (error instanceof Error && error.message) {
console.log(` ${c.dim}${error.message}${c.reset}`);
}
} else {
console.log(` GPU: ${c.yellow}none${c.reset} (running on CPU — models will be slow)`);
console.log(` ${c.dim}Tip: Install CUDA, Vulkan, or Metal support for GPU acceleration.${c.reset}`);
}
console.log(` CPU: ${device.cpuCores} math cores`);
} catch (error) {
console.log(` Status: ${c.dim}skipped${c.reset} (status probe does not build llama.cpp backends)`);
if (error instanceof Error && error.message) {
console.log(` ${c.dim}${error.message}${c.reset}`);
}
}

View File

@ -381,6 +381,12 @@ describe("CLI Status Command", () => {
// Should show collection info
expect(stdout).toContain("Collection");
});
test("skips device probing by default", async () => {
const { stdout, exitCode } = await runQmd(["status"]);
expect(exitCode).toBe(0);
expect(stdout).not.toContain("Device");
});
});
describe("CLI Search Command", () => {