mirror of
https://github.com/yusufipk/dikte.git
synced 2026-09-12 03:16:19 +00:00
Say what the local models are running on
"Use the graphics card" was a flag and nothing else: whisper got -ng when it was off, llama got -ngl 99 or 0, and nobody looked at what happened next. A build without a GPU backend runs on the processor while the box stays ticked, which is what the release archives for Linux do, every time. Nothing anywhere reported whether the local server was even up. Both servers already say where the model went, in the log Dikte captures. It is read back once the server reports ready and turned into a backend, a card and, for llama, the layers it offloaded. The verdict comes from what whisper committed to -- "using X backend" and the model buffer -- rather than from the devices it merely listed: a card that is found and then fails to initialise sends it back to the processor, and the listing alone would have called that a graphics card. A log that says nothing stays "could not tell" instead of being guessed at; a hand-built macOS whisper has Metal compiled in and prints no backend line at all. The state is then somewhere to be seen. Server.state() is a snapshot of the process and what it settled on, and it reaches `dikte status`, `dikte doctor` and a line under each local model box in the settings window. doctor reads the log from disk when no instance is running, so it still answers on a machine where Dikte is closed, and it says which of the three it is: what the last run used, that the last run said nothing, or that none ever ran here. Where the card was asked for and not obtained, the line says which of the two it is -- none was found, or this build carries none -- because only the second is worth replacing a download over. The tests grew a second isolation. They read ggml's own data directory, which is the real one on the machine running them, and program_path prefers a whisper-server on the PATH, so the suite answered from whatever the developer happened to have installed. Both are now the test's own. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019zqCqhmeaNT6m1GPp8ZWPp
This commit is contained in:
@@ -24,6 +24,7 @@ from unittest import mock
|
||||
|
||||
from dikte import assistant
|
||||
from dikte import config as cfg
|
||||
from dikte import ggml
|
||||
from dikte import i18n
|
||||
from dikte import update
|
||||
|
||||
@@ -91,6 +92,22 @@ class DikteTest(unittest.TestCase):
|
||||
# down when it last ran.
|
||||
self.patch_attr(assistant, "SESSION_FILE", data_dir / "assistant.json")
|
||||
self.patch_attr(update, "STATE_FILE", data_dir / "update.json")
|
||||
# ggml resolves its own three from paths.DATA_DIR at import, the same
|
||||
# way cfg does. Left alone, a test asking what is installed or what the
|
||||
# last server ran on would be reading whatever this machine happens to
|
||||
# have downloaded, and passing or failing on somebody's home directory.
|
||||
# program_path prefers a whisper-server or llama-server on the PATH
|
||||
# over the copy Dikte downloaded, so on a machine with whisper.cpp
|
||||
# installed these tests would be answering from that copy instead of
|
||||
# from the install they set up. Every other tool still resolves; the
|
||||
# tests that are about the system build patch this again themselves.
|
||||
_which = shutil.which
|
||||
self.patch_attr(shutil, "which", lambda tool, *args, **rest: (
|
||||
None if tool in ("whisper-server", "llama-server")
|
||||
else _which(tool, *args, **rest)))
|
||||
self.patch_attr(ggml, "DATA_DIR", data_dir)
|
||||
self.patch_attr(ggml, "BIN_DIR", data_dir / "bin")
|
||||
self.patch_attr(ggml, "MODELS_DIR", data_dir / "models")
|
||||
|
||||
i18n.set_language("en")
|
||||
self.addCleanup(i18n.set_language, "en")
|
||||
|
||||
@@ -775,6 +775,114 @@ class Replies(DikteTest):
|
||||
self.assertFalse(launched.called)
|
||||
|
||||
|
||||
class LocalModels(DikteTest):
|
||||
"""Whether the model on this machine is loaded, and what it is loaded on."""
|
||||
|
||||
def status(self, local, **rest):
|
||||
reply = {"ok": True, "running": True, "dictation": "idle", "ask": "idle",
|
||||
"meeting": "idle", "listener": True, "local": local, **rest}
|
||||
with mock.patch.object(ipc, "send", return_value=reply), \
|
||||
captured() as (out, _err):
|
||||
cli.cmd_status(Options(json=False))
|
||||
return out.getvalue()
|
||||
|
||||
def entry(self, **values):
|
||||
base = {"running": True, "used": True, "pid": 7, "port": 4321,
|
||||
"model": "ggml-small.bin", "gpu_wanted": True,
|
||||
"backend": "CUDA", "device": "RTX 4070", "layers": "",
|
||||
"available": ["CUDA", "CPU"]}
|
||||
base.update(values)
|
||||
return base
|
||||
|
||||
def test_a_loaded_model_says_what_it_is_loaded_on(self):
|
||||
line = self.status({"whisper": self.entry()})
|
||||
self.assertIn("whisper:", line)
|
||||
self.assertIn("loaded on the graphics card (CUDA, RTX 4070)", line)
|
||||
self.assertIn("ggml-small.bin", line)
|
||||
|
||||
def test_a_card_asked_for_and_not_found_is_said_out_loud(self):
|
||||
line = self.status({"whisper": self.entry(
|
||||
backend="CPU", device="CPU", available=["CPU"])})
|
||||
self.assertIn("loaded on the processor", line)
|
||||
self.assertIn("this build carries none", line)
|
||||
|
||||
def test_a_card_the_build_could_have_used_says_something_else(self):
|
||||
line = self.status({"whisper": self.entry(
|
||||
backend="CPU", device="CPU", available=["CUDA", "CPU"])})
|
||||
self.assertIn("none was found", line)
|
||||
self.assertNotIn("carries none", line)
|
||||
|
||||
def test_a_card_nobody_asked_for_is_not_a_complaint(self):
|
||||
line = self.status({"whisper": self.entry(
|
||||
backend="CPU", device="CPU", gpu_wanted=False, available=["CPU"])})
|
||||
self.assertIn("loaded on the processor", line)
|
||||
self.assertNotIn("switched on", line)
|
||||
|
||||
def test_a_model_that_is_wanted_and_not_loaded_says_so(self):
|
||||
line = self.status({"whisper": self.entry(running=False)})
|
||||
self.assertIn("whisper:", line)
|
||||
self.assertIn("not loaded", line)
|
||||
|
||||
def test_a_model_neither_used_nor_loaded_is_not_worth_a_line(self):
|
||||
line = self.status({"llama": self.entry(running=False, used=False)})
|
||||
self.assertNotIn("llama", line)
|
||||
|
||||
def test_an_instance_too_old_to_have_been_asked_says_nothing(self):
|
||||
reply = {"ok": True, "running": True, "dictation": "idle", "ask": "idle",
|
||||
"meeting": "idle", "listener": True}
|
||||
with mock.patch.object(ipc, "send", return_value=reply), \
|
||||
captured() as (out, _err):
|
||||
cli.cmd_status(Options(json=False))
|
||||
self.assertNotIn("whisper", out.getvalue())
|
||||
|
||||
# ---- doctor, which can be asked with nothing running -----------------
|
||||
|
||||
def doctor(self, as_json=False, **settings):
|
||||
self.write_config(settings)
|
||||
with mock.patch.object(ipc, "send", return_value=None), \
|
||||
captured() as (out, _err):
|
||||
cli.cmd_doctor(Options(json=as_json))
|
||||
return json.loads(out.getvalue()) if as_json else out.getvalue()
|
||||
|
||||
def log(self, text):
|
||||
path = ggml.DATA_DIR / "whisper-server.log"
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(text)
|
||||
|
||||
def test_with_nothing_running_the_last_start_is_read_off_its_log(self):
|
||||
self.log("load_backend: loaded CPU backend from /x.so\n"
|
||||
"whisper_backend_init_gpu: device 0: CPU (type: 0)\n"
|
||||
"whisper_backend_init_gpu: no GPU found\n")
|
||||
line = self.doctor(transcribe_provider="local", local_gpu=True)
|
||||
self.assertIn("last run on the processor", line)
|
||||
self.assertIn("this build carries none", line)
|
||||
|
||||
def test_a_run_that_named_no_backend_is_not_read_as_no_run_at_all(self):
|
||||
# A log with nothing recognisable in it still says a server started
|
||||
# here once, which is a different thing from never having started.
|
||||
self.log("whisper_model_load: model size = 147.37 MB\n")
|
||||
line = self.doctor(transcribe_provider="local")
|
||||
self.assertIn("said nothing about what it was running on", line)
|
||||
self.assertNotIn("never run here", line)
|
||||
|
||||
def test_a_machine_that_never_ran_one_is_not_made_up_a_history_for(self):
|
||||
line = self.doctor(transcribe_provider="local")
|
||||
self.assertIn("never run here", line)
|
||||
|
||||
def test_a_setup_that_transcribes_in_the_cloud_reads_about_none_of_it(self):
|
||||
line = self.doctor(transcribe_provider="openai", cleanup_enabled=False)
|
||||
self.assertNotIn("whisper ", line)
|
||||
self.assertNotIn("never run here", line)
|
||||
|
||||
def test_an_instance_that_cannot_be_asked_is_not_read_as_a_no(self):
|
||||
"""It used to print "not loaded", which is a different claim."""
|
||||
self.write_config({"transcribe_provider": "local"})
|
||||
with mock.patch.object(ipc, "send", return_value={"ok": True}), \
|
||||
captured() as (out, _err):
|
||||
cli.cmd_doctor(Options(json=False))
|
||||
self.assertIn("too old to say", out.getvalue())
|
||||
|
||||
|
||||
class TranscribeRunsHere(DikteTest):
|
||||
"""`dikte transcribe` runs in this process, not in the instance."""
|
||||
|
||||
|
||||
@@ -525,6 +525,180 @@ class Catalogue(Local):
|
||||
"model.gguf")
|
||||
|
||||
|
||||
# --- what it ended up running on ------------------------------------------
|
||||
|
||||
|
||||
# Trimmed from real logs. The first is this project's own bug report: the
|
||||
# graphics card is switched on, whisper asked for one, and the build had none
|
||||
# to give.
|
||||
WHISPER_CPU = """\
|
||||
load_backend: loaded CPU backend from /opt/whisper/libggml-cpu-haswell.so
|
||||
whisper_init_from_file_with_params_no_state: loading model from 'ggml-small.bin'
|
||||
whisper_init_with_params_no_state: use gpu = 1
|
||||
whisper_model_load: CPU total size = 189.49 MB
|
||||
whisper_backend_init_gpu: device 0: CPU (type: 0)
|
||||
whisper_backend_init_gpu: no GPU found
|
||||
"""
|
||||
|
||||
WHISPER_CUDA = """\
|
||||
load_backend: loaded CUDA backend from /opt/whisper/libggml-cuda.so
|
||||
load_backend: loaded CPU backend from /opt/whisper/libggml-cpu-haswell.so
|
||||
whisper_init_with_params_no_state: use gpu = 1
|
||||
whisper_model_load: CUDA0 total size = 189.49 MB
|
||||
whisper_backend_init_gpu: device 0: NVIDIA GeForce RTX 4070 (type: 1)
|
||||
whisper_backend_init_gpu: using CUDA0 backend
|
||||
"""
|
||||
|
||||
# A card listed, tried, and refused: whisper says so and carries on without it,
|
||||
# and the weights stay where they were put. Reading the listing alone would
|
||||
# report a graphics card that is doing nothing.
|
||||
WHISPER_GPU_FAILED = """\
|
||||
load_backend: loaded Vulkan backend from /usr/lib/ggml/libggml-vulkan.so
|
||||
load_backend: loaded CPU backend from /usr/lib/ggml/libggml-cpu-haswell.so
|
||||
whisper_model_load: CPU total size = 189.49 MB
|
||||
whisper_backend_init_gpu: device 0: Vulkan0 (type: 1)
|
||||
whisper_backend_init_gpu: found GPU device 0: Vulkan0 (type: 1, cnt: 0)
|
||||
whisper_backend_init_gpu: failed to initialize Vulkan0 backend
|
||||
"""
|
||||
|
||||
# Both backends in one build. The Vulkan listing is there and is not the one
|
||||
# that ran, so naming the card out of it would name the wrong device.
|
||||
WHISPER_MIXED = """\
|
||||
ggml_vulkan: Found 1 Vulkan devices:
|
||||
ggml_vulkan: 0 = Intel UHD Graphics 770 (ANV TGL) (anv) | uma: 1
|
||||
load_backend: loaded CUDA backend from /opt/whisper/libggml-cuda.so
|
||||
load_backend: loaded Vulkan backend from /opt/whisper/libggml-vulkan.so
|
||||
load_backend: loaded CPU backend from /opt/whisper/libggml-cpu-haswell.so
|
||||
Device 0: NVIDIA GeForce RTX 4070, compute capability 8.9, VMM: yes
|
||||
whisper_model_load: CUDA0 total size = 189.49 MB
|
||||
whisper_backend_init_gpu: device 0: CUDA0 (type: 1)
|
||||
whisper_backend_init_gpu: using CUDA0 backend
|
||||
"""
|
||||
|
||||
# The same start on a card whisper names only by its slot. The card's own name
|
||||
# is one line further up, printed by the backend as it enumerates.
|
||||
WHISPER_VULKAN = """\
|
||||
ggml_vulkan: Found 1 Vulkan devices:
|
||||
ggml_vulkan: 0 = AMD Radeon RX 6600 (RADV NAVI23) (radv) | uma: 0 | fp16: dot2
|
||||
load_backend: loaded Vulkan backend from /usr/lib/ggml/libggml-vulkan.so
|
||||
load_backend: loaded CPU backend from /usr/lib/ggml/libggml-cpu-haswell.so
|
||||
whisper_model_load: Vulkan0 total size = 189.49 MB
|
||||
whisper_backend_init_gpu: device 0: Vulkan0 (type: 1)
|
||||
whisper_backend_init_gpu: using Vulkan0 backend
|
||||
"""
|
||||
|
||||
# A whisper built by hand on a Mac: Metal is compiled in rather than loaded, so
|
||||
# there is no line to read and no honest answer but "it did not say".
|
||||
WHISPER_QUIET = """\
|
||||
whisper_init_from_file_with_params_no_state: loading model from 'ggml-base.bin'
|
||||
whisper_model_load: model size = 147.37 MB
|
||||
"""
|
||||
|
||||
LLAMA_GPU = """\
|
||||
load_backend: loaded Vulkan backend from /opt/llama/libggml-vulkan.so
|
||||
load_backend: loaded CPU backend from /opt/llama/libggml-cpu.so
|
||||
load_tensors: offloading 28 repeating layers to GPU
|
||||
load_tensors: offloaded 29/29 layers to GPU
|
||||
"""
|
||||
|
||||
LLAMA_CPU = """\
|
||||
load_backend: loaded Vulkan backend from /opt/llama/libggml-vulkan.so
|
||||
load_backend: loaded CPU backend from /opt/llama/libggml-cpu.so
|
||||
load_tensors: offloaded 0/29 layers to GPU
|
||||
"""
|
||||
|
||||
|
||||
class WhatItRunsOn(Local):
|
||||
"""Reading the backend back out of the log the server wrote."""
|
||||
|
||||
def log(self, text):
|
||||
path = self.path("server.log")
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(text)
|
||||
return path
|
||||
|
||||
def read(self, program, text):
|
||||
return ggml._read_accel(program, self.log(text))
|
||||
|
||||
def test_a_card_that_was_asked_for_and_not_found_is_the_processor(self):
|
||||
accel = self.read(ggml.WHISPER, WHISPER_CPU)
|
||||
self.assertEqual(accel.backend, "CPU")
|
||||
self.assertEqual(ggml.accel_kind(accel), "cpu")
|
||||
|
||||
def test_a_build_with_nothing_but_a_processor_backend_says_so(self):
|
||||
self.assertTrue(ggml.cpu_only_build(self.read(ggml.WHISPER, WHISPER_CPU)))
|
||||
self.assertFalse(ggml.cpu_only_build(self.read(ggml.WHISPER, WHISPER_CUDA)))
|
||||
|
||||
def test_a_card_that_was_found_is_named(self):
|
||||
accel = self.read(ggml.WHISPER, WHISPER_CUDA)
|
||||
self.assertEqual(accel.backend, "CUDA")
|
||||
self.assertEqual(accel.device, "NVIDIA GeForce RTX 4070")
|
||||
self.assertEqual(ggml.accel_kind(accel), "gpu")
|
||||
self.assertEqual(ggml.accel_detail(accel),
|
||||
"CUDA, NVIDIA GeForce RTX 4070")
|
||||
|
||||
def test_a_card_named_only_by_its_slot_is_looked_up(self):
|
||||
accel = self.read(ggml.WHISPER, WHISPER_VULKAN)
|
||||
self.assertEqual(accel.backend, "Vulkan")
|
||||
# "Vulkan0" says which slot; the point of the line is which card.
|
||||
self.assertEqual(accel.device, "AMD Radeon RX 6600 (RADV NAVI23)")
|
||||
|
||||
def test_the_driver_behind_the_card_is_not_part_of_its_name(self):
|
||||
# "(radv)" is how it is reached; "(RADV NAVI23)" is what it is called.
|
||||
self.assertNotIn("(radv)",
|
||||
self.read(ggml.WHISPER, WHISPER_VULKAN).device)
|
||||
|
||||
def test_a_card_that_failed_to_start_is_not_a_card_in_use(self):
|
||||
# It was listed, it was tried, it did not work, and whisper went on
|
||||
# without it. The listing alone would have called this a graphics card.
|
||||
accel = self.read(ggml.WHISPER, WHISPER_GPU_FAILED)
|
||||
self.assertEqual(accel.backend, "CPU")
|
||||
self.assertEqual(ggml.accel_kind(accel), "cpu")
|
||||
|
||||
def test_the_card_named_is_the_one_that_ran(self):
|
||||
accel = self.read(ggml.WHISPER, WHISPER_MIXED)
|
||||
self.assertEqual(accel.backend, "CUDA")
|
||||
self.assertEqual(accel.device, "NVIDIA GeForce RTX 4070")
|
||||
self.assertNotIn("Intel", ggml.accel_detail(accel))
|
||||
|
||||
def test_a_slot_number_is_not_a_name(self):
|
||||
# "Vulkan0" says which slot; with no listing to look it up in, saying
|
||||
# nothing beats saying that.
|
||||
self.assertEqual(self.read(ggml.LLAMA, LLAMA_GPU).device, "")
|
||||
|
||||
def test_a_log_that_says_nothing_is_not_guessed_at(self):
|
||||
accel = self.read(ggml.WHISPER, WHISPER_QUIET)
|
||||
self.assertEqual(accel.backend, "")
|
||||
self.assertEqual(ggml.accel_kind(accel), "unknown")
|
||||
|
||||
def test_a_log_that_is_not_there_is_not_guessed_at_either(self):
|
||||
self.assertEqual(ggml._read_accel(ggml.WHISPER, self.path("gone.log")),
|
||||
ggml.NO_ACCEL)
|
||||
|
||||
def test_the_layers_llama_offloaded_are_read_back(self):
|
||||
accel = self.read(ggml.LLAMA, LLAMA_GPU)
|
||||
self.assertEqual(accel.backend, "Vulkan")
|
||||
self.assertEqual(accel.layers, "29/29")
|
||||
self.assertEqual(ggml.accel_detail(accel), "Vulkan, 29/29 layers")
|
||||
|
||||
def test_a_llama_that_offloaded_nothing_is_on_the_processor(self):
|
||||
accel = self.read(ggml.LLAMA, LLAMA_CPU)
|
||||
self.assertEqual(accel.backend, "CPU")
|
||||
self.assertEqual(ggml.accel_kind(accel), "cpu")
|
||||
# The build could have used the card; this run did not.
|
||||
self.assertFalse(ggml.cpu_only_build(accel))
|
||||
|
||||
def test_the_processor_is_not_named_twice(self):
|
||||
# whisper prints CPU as the backend and as the device, and saying it
|
||||
# twice reads like two different things.
|
||||
self.assertEqual(ggml.accel_detail(self.read(ggml.WHISPER, WHISPER_CPU)),
|
||||
"CPU")
|
||||
|
||||
def test_nothing_is_running_is_not_a_backend(self):
|
||||
self.assertEqual(ggml.accel_kind({"running": False, "backend": "CUDA"}),
|
||||
"off")
|
||||
|
||||
|
||||
# --- keeping a server alive -----------------------------------------------
|
||||
|
||||
|
||||
@@ -544,6 +718,18 @@ STAND_IN = textwrap.dedent("""
|
||||
print("could not load model: no such file")
|
||||
sys.exit(2)
|
||||
|
||||
# The startup chatter a real server prints before it binds, so that the
|
||||
# log has something for _read_accel to find. Flushed, because stdout here
|
||||
# is a file and nothing would reach it before the port opened.
|
||||
if "--backend" in args:
|
||||
print("load_backend: loaded " + opt("--backend") + " backend from /x.so",
|
||||
flush=True)
|
||||
print("whisper_backend_init_gpu: device 0: Test Card (type: 1)",
|
||||
flush=True)
|
||||
# The line that says one of them worked, which is the one read back.
|
||||
print("whisper_backend_init_gpu: using " + opt("--backend") + "0 backend",
|
||||
flush=True)
|
||||
|
||||
started = time.monotonic()
|
||||
healthy_after = float(opt("--healthy-after", "0"))
|
||||
|
||||
@@ -602,6 +788,50 @@ class Servers(Local):
|
||||
self.assertRegex(url, r"^http://127\.0\.0\.1:\d+/v1$")
|
||||
self.assertTrue(server.running)
|
||||
|
||||
def test_nothing_started_is_a_state_saying_so(self):
|
||||
state = self.server().state()
|
||||
self.assertFalse(state["running"])
|
||||
self.assertEqual(ggml.accel_kind(state), "off")
|
||||
|
||||
def test_a_running_server_says_what_it_settled_on(self):
|
||||
server = self.server(extra=["--backend", "CUDA"], gpu=True)
|
||||
server.serve()
|
||||
state = server.state()
|
||||
self.assertTrue(state["running"])
|
||||
self.assertIn(f":{state['port']}/v1", server.base_url())
|
||||
self.assertEqual(state["backend"], "CUDA")
|
||||
self.assertEqual(state["device"], "Test Card")
|
||||
self.assertTrue(state["gpu_wanted"])
|
||||
self.assertEqual(ggml.accel_kind(state), "gpu")
|
||||
|
||||
def test_a_setting_changed_mid_start_does_not_rename_what_is_running(self):
|
||||
# A save that lands while the model is being read in finds no process
|
||||
# to stop, so it changes the settings under a start already in flight.
|
||||
# The line must name the model that is loaded, not the one that will be.
|
||||
server = self.server(model="first")
|
||||
launch = server._launch
|
||||
|
||||
def during(settings):
|
||||
result = launch(settings)
|
||||
server.configure(model="second")
|
||||
return result
|
||||
|
||||
self.patch_attr(server, "_launch", during)
|
||||
server.serve()
|
||||
self.assertEqual(server.state()["model"], "first")
|
||||
self.assertEqual(server.settings()["model"], "second")
|
||||
|
||||
def test_stopping_takes_the_backend_with_it(self):
|
||||
server = self.server(extra=["--backend", "CUDA"])
|
||||
server.serve()
|
||||
server.stop()
|
||||
self.assertEqual(server.state()["backend"], "")
|
||||
|
||||
def test_a_server_that_announced_nothing_is_not_guessed_at(self):
|
||||
server = self.server()
|
||||
server.serve()
|
||||
self.assertEqual(ggml.accel_kind(server.state()), "unknown")
|
||||
|
||||
def test_the_second_call_does_not_start_a_second_one(self):
|
||||
server = self.server()
|
||||
first = server.serve()
|
||||
|
||||
+55
-1
@@ -13,7 +13,7 @@ from typing import ClassVar
|
||||
from unittest import mock
|
||||
|
||||
from PyQt6.QtCore import QPoint, QPointF, Qt
|
||||
from PyQt6.QtGui import QWheelEvent
|
||||
from PyQt6.QtGui import QHideEvent, QShowEvent, QWheelEvent
|
||||
from PyQt6.QtWidgets import QApplication, QMessageBox
|
||||
|
||||
from dikte import audio
|
||||
@@ -1177,6 +1177,60 @@ class LocalModels(DikteTest):
|
||||
self.window(conf)._save()
|
||||
self.assertEqual(conf["local_model"], "ggml-large-v3-turbo-q5_0.bin")
|
||||
|
||||
def state(self, **values):
|
||||
base = {"running": True, "pid": 3, "port": 4321, "model": "ggml-small.bin",
|
||||
"gpu_wanted": True, "backend": "CUDA", "device": "RTX 4070",
|
||||
"layers": "", "available": ["CUDA", "CPU"]}
|
||||
base.update(values)
|
||||
return base
|
||||
|
||||
def shown(self, **values):
|
||||
"""The line the window writes under the local model boxes."""
|
||||
window = self.window(self.config(transcribe_provider="local"))
|
||||
with mock.patch.object(ggml, "state",
|
||||
return_value={"whisper": self.state(**values),
|
||||
"llama": self.state(running=False)}):
|
||||
window._show_local_state()
|
||||
return window.local_state.text(), window.local_llm_state.text()
|
||||
|
||||
def test_a_loaded_model_says_which_card_it_is_on(self):
|
||||
whisper, llm = self.shown()
|
||||
self.assertIn("graphics card", whisper)
|
||||
self.assertIn("RTX 4070", whisper)
|
||||
# The other box is about the other model, and that one is not loaded.
|
||||
self.assertIn("Not loaded", llm)
|
||||
|
||||
def test_a_card_asked_for_and_missing_is_not_left_to_be_guessed_at(self):
|
||||
whisper, _ = self.shown(backend="CPU", device="CPU", available=["CPU"])
|
||||
self.assertIn("processor", whisper)
|
||||
self.assertIn("no graphics backend", whisper)
|
||||
|
||||
def test_a_build_that_could_have_used_one_says_the_other_thing(self):
|
||||
whisper, _ = self.shown(backend="CPU", device="CPU",
|
||||
available=["CUDA", "CPU"])
|
||||
self.assertIn("none was found", whisper)
|
||||
self.assertNotIn("no graphics backend", whisper)
|
||||
|
||||
def test_a_processor_nobody_argued_about_is_stated_plainly(self):
|
||||
whisper, _ = self.shown(backend="CPU", device="CPU", gpu_wanted=False,
|
||||
available=["CPU"])
|
||||
self.assertEqual(whisper, "Loaded on the processor (CPU).")
|
||||
|
||||
def test_a_server_that_said_nothing_is_not_answered_for(self):
|
||||
"""A whisper built by hand on a Mac prints no backend line at all."""
|
||||
whisper, _ = self.shown(backend="", device="", available=[])
|
||||
self.assertIn("did not say", whisper)
|
||||
|
||||
def test_the_line_stops_being_written_while_the_window_is_away(self):
|
||||
# The events rather than show() and hide(): showing the window for real
|
||||
# would send the same event down to the download boxes, which answer it
|
||||
# by asking Hugging Face what models there are.
|
||||
window = self.window(self.config(transcribe_provider="local"))
|
||||
window.showEvent(QShowEvent())
|
||||
self.assertTrue(window._local_state_timer.isActive())
|
||||
window.hideEvent(QHideEvent())
|
||||
self.assertFalse(window._local_state_timer.isActive())
|
||||
|
||||
def test_nothing_is_fetched_for_a_window_nobody_opened(self):
|
||||
# DikteTest closes the network, so a request would fail the test. The
|
||||
# lists are asked for when the box is shown, not when it is built.
|
||||
|
||||
Reference in New Issue
Block a user