diff --git a/LOCAL_WHISPER_SETUP.md b/LOCAL_WHISPER_SETUP.md index 1d26d5be56..53f4bd1c5a 100644 --- a/LOCAL_WHISPER_SETUP.md +++ b/LOCAL_WHISPER_SETUP.md @@ -28,10 +28,11 @@ The first transcription will download the model automatically. Local Whisper can run on your GPU for much faster transcription: - **macOS**: Metal acceleration is built in — no setup needed on Apple Silicon -- **NVIDIA (Windows/Linux)**: one-click CUDA runtime download from the GPU card in the transcription model picker -- **AMD / Intel (Windows/Linux)**: one-click Vulkan runtime download from the same GPU card — covers Radeon and Arc/integrated GPUs +- **NVIDIA (Windows/Linux)**: CUDA acceleration; Linux requires a current compatible NVIDIA driver, but not the CUDA Toolkit +- **AMD / Intel (Windows/Linux)**: Vulkan acceleration for Radeon, Arc, and integrated GPUs +- **NVIDIA alternative**: Vulkan is also supported and is used automatically when its runtime is installed and CUDA fails -The GPU runtime is downloaded on demand with SHA-256-verified checksums. If the GPU server crashes or fails to start (unsupported GPU, out of VRAM), OpenWhispr automatically falls back to CPU transcription and shows a notice — dictation keeps working. +GPU runtimes are downloaded on demand with SHA-256-verified checksums. If one fails, OpenWhispr uses the next available backend and finally CPU, so dictation keeps working. ## How It Works @@ -45,7 +46,7 @@ OpenWhispr uses whisper.cpp, a high-performance C++ implementation of OpenAI's W - **Disk Space**: 75MB–3GB depending on model - **RAM**: 1GB–10GB depending on model -- **No additional dependencies required** - whisper.cpp is bundled in packaged builds +- **GPU driver**: required for GPU acceleration; the CUDA Toolkit is not required ## Running From Source diff --git a/main.js b/main.js index f087be23a9..8d6ab4ec1e 100644 --- a/main.js +++ b/main.js @@ -573,10 +573,40 @@ function initializeCoreManagers() { // with no pack on disk only happens via such data loss. recordOnce gates // each pack to one notice so a dismissed toast doesn't return every launch. const gpuPacks = [ - { manager: whisperCudaManager, enabledEnvVar: "WHISPER_CUDA_ENABLED", group: "whisper" }, - { manager: whisperVulkanManager, enabledEnvVar: "WHISPER_VULKAN_ENABLED", group: "whisper" }, + { + manager: whisperCudaManager, + enabledEnvVar: "WHISPER_CUDA_ENABLED", + group: "whisper", + backend: "cuda", + }, + { + manager: whisperVulkanManager, + enabledEnvVar: "WHISPER_VULKAN_ENABLED", + group: "whisper", + backend: "vulkan", + }, { manager: llamaVulkanManager, enabledEnvVar: "LLAMA_VULKAN_ENABLED" }, ]; + // A pack whose backend failed here and then vanished (a distro launcher + // deletes it, #2168) was no loss: drop its flag as a delete would, so no + // notice names it, even after an upgrade clears the remembered failure. + const failedBackends = WhisperManager.resolveFailedGpuBackends(process.env.WHISPER_GPU_FAILED); + for (const { manager, enabledEnvVar, backend } of gpuPacks) { + if ( + failedBackends.includes(backend) && + process.env[enabledEnvVar] === "true" && + !manager.isDownloaded() && + !manager.needsUpdate() + ) { + delete process.env[enabledEnvVar]; + environmentManager.removeKeyFromEnvFile(enabledEnvVar).catch((err) => { + debugLogger.error("Failed to clear a vanished GPU pack's flag from .env", { + key: enabledEnvVar, + error: err.message, + }); + }); + } + } const orphanedPacks = detectOrphanedGpuPacks(gpuPacks); if (orphanedPacks.length > 0) { require("./src/helpers/gpuPackMigrationNotice").recordOnce(orphanedPacks); diff --git a/src/components/TranscriptionModelPicker.tsx b/src/components/TranscriptionModelPicker.tsx index 8d5b1e57c1..bfc8096166 100644 --- a/src/components/TranscriptionModelPicker.tsx +++ b/src/components/TranscriptionModelPicker.tsx @@ -514,6 +514,7 @@ export default function TranscriptionModelPicker({ const [gpuActivating, setGpuActivating] = useState(false); // Live truth from the running server; "active" is never inferred from a download const [gpuActive, setGpuActive] = useState(false); + const gpuBackendName = gpuBackend === "cuda" ? "CUDA" : "Vulkan"; useEffect(() => { const organization = getSelectedASROrganization(selectedLocalProvider, selectedLocalModel); @@ -1623,21 +1624,21 @@ export default function TranscriptionModelPicker({ <> - {t("gpu.activating")} + {t("gpu.activatingBackend", { backend: gpuBackendName })} ) : gpuActive ? ( <> - {t("gpu.active")} + {t("gpu.activeBackend", { backend: gpuBackendName })} ) : ( <> - {t("gpu.ready")} + {t("gpu.readyBackend", { backend: gpuBackendName })} )} @@ -1667,6 +1668,9 @@ export default function TranscriptionModelPicker({

{t("gpu.transcriptionBanner")}

+

+ {t(gpuBackend === "cuda" ? "gpu.cudaDescription" : "gpu.vulkanDescription")} +