From b013f7947c0efb8f1e1fb83bb690f872cc1b468f Mon Sep 17 00:00:00 2001 From: Gabriel Stein Date: Wed, 12 Aug 2026 15:43:05 -0700 Subject: [PATCH] feat(gpu): offer the CUDA pack to Pascal cards (0.0.9 kernel floor) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit whisper.cpp release 0.0.9 ships sm_61 kernels (and compute_61 PTX that covers Volta via driver JIT), so the offering gate drops from 7.5 to 6.1 — GTX 10-series cards get the CUDA pack instead of being routed to Vulkan. Maxwell and older stay on Vulkan. The picker now prefers whichever backend is already installed: a Pascal user running the Vulkan pack keeps their working chip instead of being re-prompted with a 772 MB CUDA download the moment the gate opens. This matches the runtime resolver, which only prefers CUDA once it is actually downloaded. HOLD FOR HARDWARE SMOKE TEST on a Pascal card before merging (CI proves sm_61 compiles; only a real GPU proves the kernels run). --- src/components/TranscriptionModelPicker.tsx | 18 +++++++++++------- src/utils/gpuDetection.js | 15 ++++++++------- test/helpers/gpuDetection.test.js | 15 +++++++++++---- 3 files changed, 30 insertions(+), 18 deletions(-) diff --git a/src/components/TranscriptionModelPicker.tsx b/src/components/TranscriptionModelPicker.tsx index 24d5a34df9..03d64483d2 100644 --- a/src/components/TranscriptionModelPicker.tsx +++ b/src/components/TranscriptionModelPicker.tsx @@ -597,17 +597,21 @@ export default function TranscriptionModelPicker({ if (getCachedPlatform() === "darwin") return; const detect = async () => { try { - // Cards below the CUDA build's kernel floor (e.g. Pascal) crash at the + const [cuda, vulkan] = await Promise.all([ + window.electronAPI?.getCudaWhisperStatus?.(), + window.electronAPI?.getVulkanWhisperStatus?.(), + ]); + // Cards below the CUDA build's kernel floor (e.g. Maxwell) crash at the // first kernel launch, so they get the Vulkan pack like AMD/Intel GPUs. - const cuda = await window.electronAPI?.getCudaWhisperStatus?.(); - if (cuda?.gpuInfo.hasNvidiaGpu && cuda.gpuInfo.cudaSupported) { + const cudaEligible = !!cuda?.gpuInfo.hasNvidiaGpu && !!cuda.gpuInfo.cudaSupported; + // Prefer the pack that's already installed: a working Vulkan setup must + // not be re-prompted to download the CUDA pack (matches the resolver, + // which only prefers CUDA when it is actually downloaded). + if (cudaEligible && (cuda.downloaded || !vulkan?.downloaded)) { setGpuBackend("cuda"); setGpuDownloaded(cuda.downloaded); setGpuFailed(!!cuda.gpuFailed); - return; - } - const vulkan = await window.electronAPI?.getVulkanWhisperStatus?.(); - if (vulkan?.vulkan.available) { + } else if (vulkan?.vulkan.available) { setGpuBackend("vulkan"); setGpuDownloaded(vulkan.downloaded); setGpuFailed(!!vulkan.gpuFailed); diff --git a/src/utils/gpuDetection.js b/src/utils/gpuDetection.js index e9fd2720fb..22ffeb873d 100644 --- a/src/utils/gpuDetection.js +++ b/src/utils/gpuDetection.js @@ -1,12 +1,13 @@ const { execFile } = require("child_process"); -// The shipped CUDA whisper build only carries kernels for Turing (compute -// capability 7.5) and newer. On older cards (e.g. Pascal / GTX 10-series, -// 6.1) the server starts, loads the model into VRAM, then aborts on the first -// kernel launch with "no kernel image is available for execution on the -// device" — so those cards must be offered Vulkan instead, which works on any -// NVIDIA GPU. -const MIN_CUDA_COMPUTE_CAP = 7.5; +// The shipped CUDA whisper build (release 0.0.9) carries kernels for Pascal +// (compute capability 6.1) and newer; 6.1's embedded PTX also covers Volta via +// driver JIT. On cards below the floor the server starts, loads the model into +// VRAM, then aborts on the first kernel launch with "no kernel image is +// available for execution on the device" — so those cards must be offered +// Vulkan instead, which works on any NVIDIA GPU. Keep this floor in lockstep +// with CUDA_ARCHITECTURES in OpenWhispr/whisper.cpp's build-binaries.yml. +const MIN_CUDA_COMPUTE_CAP = 6.1; let cachedGpuInfo = null; diff --git a/test/helpers/gpuDetection.test.js b/test/helpers/gpuDetection.test.js index 9eda41fbc5..284c9d8e6a 100644 --- a/test/helpers/gpuDetection.test.js +++ b/test/helpers/gpuDetection.test.js @@ -19,15 +19,22 @@ test("Turing and newer report cudaSupported", () => { }); }); -test("Pascal is detected but not CUDA-supported (gets Vulkan instead)", () => { +test("Pascal is CUDA-supported since the 0.0.9 build ships sm_61 kernels", () => { const info = parseNvidiaSmiGpuInfo("NVIDIA GeForce GTX 1080 Ti, 582.66, 11264, 6.1"); assert.equal(info.hasNvidiaGpu, true); assert.equal(info.computeCap, 6.1); + assert.equal(info.cudaSupported, true); +}); + +test("Maxwell is detected but not CUDA-supported (gets Vulkan instead)", () => { + const info = parseNvidiaSmiGpuInfo("NVIDIA GeForce GTX 970, 560.94, 4096, 5.2"); + assert.equal(info.hasNvidiaGpu, true); + assert.equal(info.computeCap, 5.2); assert.equal(info.cudaSupported, false); }); test("exactly the minimum compute capability is supported", () => { - const info = parseNvidiaSmiGpuInfo(`RTX 2060, 560.94, 6144, ${MIN_CUDA_COMPUTE_CAP}`); + const info = parseNvidiaSmiGpuInfo(`Some GPU, 560.94, 6144, ${MIN_CUDA_COMPUTE_CAP}`); assert.equal(info.cudaSupported, true); }); @@ -46,9 +53,9 @@ test("an unparseable compute_cap ([N/A]) stays conservative", () => { test("multi-GPU output uses the primary card", () => { const info = parseNvidiaSmiGpuInfo( - "NVIDIA GeForce GTX 1080 Ti, 582.66, 11264, 6.1\nNVIDIA T400, 582.66, 2048, 7.5" + "NVIDIA GeForce GTX 970, 560.94, 4096, 5.2\nNVIDIA T400, 582.66, 2048, 7.5" ); - assert.equal(info.gpuName, "NVIDIA GeForce GTX 1080 Ti"); + assert.equal(info.gpuName, "NVIDIA GeForce GTX 970"); assert.equal(info.cudaSupported, false); });