From 59180b8efff18736cab98811744f003f73b3cfd4 Mon Sep 17 00:00:00 2001 From: yusufipk Date: Sat, 5 Sep 2026 09:16:59 +0300 Subject: [PATCH] Say when the processor build landed instead of the Vulkan one The install falls back to upstream's CPU archive whenever Dikte's own release, the file in it, or its reviewed digest is not there, and until the package is published by hand that is every download. It happened without a word: the window said "Downloaded, version v1.9.3." either way, and a graphics card sitting idle looks exactly like one being used. The install record now carries which of the two builds landed, written only where both were on offer, and the settings window says so on the line that already reports the version. --- dikte/ggml.py | 42 ++++++++++++++++++++++++++++++++++++------ dikte/i18n.py | 3 +++ dikte/settings_ui.py | 7 +++++++ tests/test_ggml.py | 19 +++++++++++++++++++ tests/test_ui.py | 29 +++++++++++++++++++++++++++++ 5 files changed, 94 insertions(+), 6 deletions(-) diff --git a/dikte/ggml.py b/dikte/ggml.py index 00e9929..fd863b9 100644 --- a/dikte/ggml.py +++ b/dikte/ggml.py @@ -284,6 +284,17 @@ def _wanted_assets(program): return (f"bin-ubuntu-{arch}.tar.gz",) +def _managed_whisper(program, tag=""): + """Whether this machine is one Dikte publishes its own whisper-server for. + + Linux x86_64 with a Vulkan loader on it, and no version asked for by hand: + a pinned version is upstream's to answer. + """ + return (not tag and program is WHISPER and sys.platform == "linux" + and platform.machine().lower() in ("x86_64", "amd64") + and _has_vulkan()) + + def _install_record(program): return BIN_DIR / program.name / "installed.json" @@ -307,6 +318,21 @@ def installed_version(program): return _read_record(program).get("tag") or "" +def vulkan_missing(program): + """Whether what Dikte installed is the processor build on a machine the + Vulkan one was fetched for. + + The Vulkan whisper-server is a release of Dikte's own, published by hand + once the reviewed archive is built, and the install falls back to the + upstream processor build whenever that release, the file in it, or its + reviewed digest is not there. Nothing is wrong with the fallback except + that it is invisible: a graphics card sitting idle looks exactly like a + graphics card being used. + """ + return (bool(installed_program(program)) + and _read_record(program).get("backend") == "processor") + + def program_path(program, custom=""): """Which copy of the program to run, or "" when there is none. @@ -382,10 +408,8 @@ def install_program(program, tag="", on_progress=None, should_stop=None, """ repo = program.repo release_tag = tag or "latest" - managed = (not tag and program is WHISPER and sys.platform == "linux" - and platform.machine().lower() in ("x86_64", "amd64") - and _has_vulkan()) - item = None + managed = _managed_whisper(program, tag) + item, vulkan = None, False if managed: repo = DIKTE_REPO release_tag = MANAGED_WHISPER_RELEASE @@ -398,6 +422,7 @@ def install_program(program, tag="", on_progress=None, should_stop=None, item = next((a for a in assets if a.name.endswith(MANAGED_WHISPER_VULKAN) and a.sha256 == MANAGED_WHISPER_SHA256), None) + vulkan = item is not None if item: tag = MANAGED_WHISPER_VERSION @@ -476,8 +501,13 @@ def install_program(program, tag="", on_progress=None, should_stop=None, # Found under the sibling, run from the final directory. binary = into / binary.relative_to(fresh) # Written last, so the record never points at anything half-made. - _install_record(program).write_text( - json.dumps({"tag": tag, "binary": str(binary)}), encoding="utf-8") + record = {"tag": tag, "binary": str(binary)} + if managed: + # Which of the two builds this machine ended up with. Only written + # where both were on offer, so an install that never had the + # choice is not made to look like a fallback. + record["backend"] = "vulkan" if vulkan else "processor" + _install_record(program).write_text(json.dumps(record), encoding="utf-8") except OSError as exc: raise LocalError(t("Could not install {name}: {error}", name=program.name, error=exc)) from exc diff --git a/dikte/i18n.py b/dikte/i18n.py index 23ff6ff..fc7476e 100644 --- a/dikte/i18n.py +++ b/dikte/i18n.py @@ -780,6 +780,9 @@ TR = { "Not installed.": "Kurulu değil.", "Installed on the system: {path}": "Sistemde kurulu: {path}", "Downloaded, version {version}.": "İndirildi, sürüm {version}.", + "Downloaded, version {version}. There was no Vulkan build, " + "so this one runs on the processor.": + "İndirildi, sürüm {version}. Vulkan sürümü yoktu, bu sürüm işlemcide çalışıyor.", "Fetching the model list…": "Model listesi çekiliyor…", "Downloading…": "İndiriliyor…", "Downloading: {done} of {total}{share}": "İndiriliyor: {done} / {total}{share}", diff --git a/dikte/settings_ui.py b/dikte/settings_ui.py index 56771f8..c3155da 100644 --- a/dikte/settings_ui.py +++ b/dikte/settings_ui.py @@ -358,6 +358,13 @@ class LocalModelBox(QGroupBox): # for this machine and may reach the graphics card, while the # released binaries carry processor backends only. self.program_label.setText(t("Installed on the system: {path}", path=path)) + elif ggml.vulkan_missing(self.program): + # The download landed the processor build where the graphics card + # one belongs, and nothing else on this window would say so. + self.program_label.setText( + t("Downloaded, version {version}. There was no Vulkan build, " + "so this one runs on the processor.", + version=ggml.installed_version(self.program) or "?")) else: self.program_label.setText( t("Downloaded, version {version}.", diff --git a/tests/test_ggml.py b/tests/test_ggml.py index 184f6fc..8dcfa0c 100644 --- a/tests/test_ggml.py +++ b/tests/test_ggml.py @@ -259,6 +259,7 @@ class InstallProgram(Local): "whisper-bin-ubuntu-vulkan-x64.tar.gz")) self.assertTrue(os.path.isfile(path)) self.assertEqual("v1.9.3", ggml.installed_version(ggml.WHISPER)) + self.assertFalse(ggml.vulkan_missing(ggml.WHISPER)) def test_an_explicit_whisper_version_still_comes_from_upstream(self): self.patch_attr(ggml, "_arch", lambda: "x64") @@ -339,6 +340,24 @@ class InstallProgram(Local): "whisper-bin-ubuntu-x64.tar.gz")) self.assertTrue(os.path.isfile(path)) + def test_a_fallback_to_the_processor_build_is_there_to_be_shown(self): + """Until the Vulkan package is published every download lands the + processor build, and a graphics card sitting idle looks exactly like + one being used. The window asks this and says so.""" + self.patch_attr(ggml, "_arch", lambda: "x64") + self.patch_attr(ggml, "_has_vulkan", lambda: True) + managed = self.release("Dikte-1.1.0-x86_64.AppImage") + managed["tag_name"] = "whisper.cpp-v1.9.3" + upstream = self.release("whisper-bin-ubuntu-x64.tar.gz") + with fake_urlopen(managed, upstream, body(self.archive)): + ggml.install_program(ggml.WHISPER) + self.assertTrue(ggml.vulkan_missing(ggml.WHISPER)) + + def test_a_machine_with_no_vulkan_is_not_told_it_is_missing_one(self): + # Nothing was on offer to fall back from, so there is nothing to say. + self.install("whisper-bin-ubuntu-x64.tar.gz") + self.assertFalse(ggml.vulkan_missing(ggml.WHISPER)) + def test_a_release_with_nothing_for_this_machine_says_so(self): self.patch_attr(ggml, "_arch", lambda: "x64") with fake_urlopen(self.release("whisper-bin-Win32.zip")): diff --git a/tests/test_ui.py b/tests/test_ui.py index 6273447..e147068 100644 --- a/tests/test_ui.py +++ b/tests/test_ui.py @@ -6,6 +6,7 @@ save, so a setting added to one half and not the other is silently reset the next time anybody presses Save. That is the failure this catches. """ +import json import os import sys import unittest @@ -1214,6 +1215,34 @@ class LocalModels(DikteTest): for row in range(box.repo.count())) self.assertGreaterEqual(view.minimumWidth(), widest) + def test_a_processor_build_where_the_vulkan_one_belongs_says_so(self): + # The Vulkan whisper-server is published by hand, and until it is + # there the download lands upstream's processor build. Said nowhere, + # an idle graphics card looks exactly like one that is being used. + binary = self.path("bin/whisper/v1.9.3/whisper-server") + binary.parent.mkdir(parents=True) + binary.write_text("") + binary.chmod(0o755) + self.path("bin/whisper/installed.json").write_text(json.dumps( + {"tag": "v1.9.3", "binary": str(binary), "backend": "processor"})) + # A whisper-server on this machine's PATH would win over the download. + self.patch_attr(ggml.shutil, "which", lambda name: None) + label = self.window(cfg.Config()).local_whisper.program_label.text() + self.assertIn("v1.9.3", label) + self.assertIn("Vulkan", label) + + def test_an_ordinary_install_is_reported_without_a_word_about_vulkan(self): + binary = self.path("bin/whisper/v1.9.3/whisper-server") + binary.parent.mkdir(parents=True) + binary.write_text("") + binary.chmod(0o755) + self.path("bin/whisper/installed.json").write_text(json.dumps( + {"tag": "v1.9.3", "binary": str(binary)})) + self.patch_attr(ggml.shutil, "which", lambda name: None) + label = self.window(cfg.Config()).local_whisper.program_label.text() + self.assertIn("v1.9.3", label) + self.assertNotIn("Vulkan", label) + def test_only_the_chosen_transcriber_is_on_screen(self): window = self.window(self.config(transcribe_provider="openai")) self.assertTrue(window.stt_form.isRowVisible(window.transcribe_model_row))