mirror of
https://github.com/yusufipk/dikte.git
synced 2026-09-11 10:56:10 +00:00
Merge pull request #76 from yusufipk/claude/model-selection-ui-organization-62dc90
Group the model lists and say which row this machine should take
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
|
||||
|
||||
@@ -95,6 +96,10 @@ class DikteTest(unittest.TestCase):
|
||||
i18n.set_language("en")
|
||||
self.addCleanup(i18n.set_language, "en")
|
||||
|
||||
# Read once and kept for the life of the process, which across a test
|
||||
# run means one test's machine answering for the next one's.
|
||||
self.patch_attr(ggml, "_MEMORY", None)
|
||||
|
||||
# cli.launch_gui replaces this process with the application when no
|
||||
# instance is running. A test that reaches it would take the whole run
|
||||
# with it and hang, so it fails loudly here instead.
|
||||
|
||||
@@ -49,6 +49,11 @@ def item(name, data, url="https://example.invalid/f", sha=True):
|
||||
hashlib.sha256(data).hexdigest() if sha else "")
|
||||
|
||||
|
||||
def listed(name, size):
|
||||
"""A row as a listing hands it over: a name and a size, no bytes."""
|
||||
return hub.Item(name, f"https://example.invalid/{name}", size, "a" * 64)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def serving(release, archive):
|
||||
"""Answer by what is being asked for rather than by what came before.
|
||||
@@ -664,6 +669,53 @@ class Catalogue(Local):
|
||||
with self.assertRaises(ggml.LocalError):
|
||||
ggml.whisper_models()
|
||||
|
||||
def test_the_speculative_decoding_heads_are_not_models(self):
|
||||
# They are the small files in a repository, so a list sorted by size
|
||||
# puts them first, where the eye lands and the click goes.
|
||||
tree = GGUF_TREE + [
|
||||
{"type": "file", "path": "dflash-Qwen3-8B-Q8_0.gguf",
|
||||
"size": 1_120_000_000, "lfs": {"oid": "f" * 64}},
|
||||
{"type": "file", "path": "eagle3-gpt-oss-20b-Q8_0.gguf",
|
||||
"size": 920_000_000, "lfs": {"oid": "0" * 64}},
|
||||
]
|
||||
with fake_urlopen(tree):
|
||||
names = [q.name for q in ggml.llm_quants("ggml-org/x-GGUF")]
|
||||
self.assertEqual(names,
|
||||
["gemma-3-4b-it-Q4_K_M.gguf", "gemma-3-4b-it-Q8_0.gguf"])
|
||||
|
||||
def test_a_speech_or_vision_repository_is_not_a_cleanup_publisher(self):
|
||||
listing = [{"id": "ggml-org/parakeet-GGUF"},
|
||||
{"id": "ggml-org/Qwen3-TTS-12Hz-1.7B-Base-GGUF"},
|
||||
{"id": "ggml-org/SmolVLM2-256M-Video-Instruct-GGUF"},
|
||||
{"id": "ggml-org/Qwen3-8B-Base-GGUF"},
|
||||
{"id": "ggml-org/SmolLM3-3B-GGUF"}]
|
||||
with fake_urlopen(listing):
|
||||
found = ggml.llm_repos()
|
||||
self.assertEqual([r for r in found if r.startswith("ggml-org/Smol")],
|
||||
["ggml-org/SmolLM3-3B-GGUF"])
|
||||
self.assertNotIn("ggml-org/parakeet-GGUF", found)
|
||||
self.assertNotIn("ggml-org/Qwen3-8B-Base-GGUF", found)
|
||||
|
||||
def test_a_publisher_is_not_dropped_for_a_word_it_happens_to_contain(self):
|
||||
# The skip marks are matched as plain substrings, and an unanchored
|
||||
# "test-" is also inside "Latest-".
|
||||
self.assertTrue(ggml.can_clean("ggml-org/Qwen3-Latest-GGUF"))
|
||||
self.assertFalse(ggml.can_clean("ggml-org/test-model-router-download"))
|
||||
|
||||
def test_a_base_model_beside_its_tuned_twin_is_dropped(self):
|
||||
# Gemma names the base model after the tuned one with the `-it` taken
|
||||
# out, so the two sit next to each other and the wrong one answers a
|
||||
# cleanup prompt by carrying on writing the transcript.
|
||||
listing = [{"id": "ggml-org/gemma-4-E2B-GGUF"},
|
||||
{"id": "ggml-org/gemma-4-E2B-it-GGUF"},
|
||||
{"id": "ggml-org/Qwen3-0.6B-GGUF"}]
|
||||
with fake_urlopen(listing):
|
||||
found = ggml.llm_repos()
|
||||
self.assertNotIn("ggml-org/gemma-4-E2B-GGUF", found)
|
||||
self.assertIn("ggml-org/gemma-4-E2B-it-GGUF", found)
|
||||
# Nothing named it, so nothing says it is the wrong half of a pair.
|
||||
self.assertIn("ggml-org/Qwen3-0.6B-GGUF", found)
|
||||
|
||||
def test_what_is_on_disk_is_read_from_disk(self):
|
||||
self.assertEqual(ggml.installed_whisper_models(), [])
|
||||
path = ggml.whisper_model_path("ggml-base.bin")
|
||||
@@ -1183,3 +1235,205 @@ class WindowsOwnership(Local):
|
||||
# from here", and only one of those makes the pid file safe to drop.
|
||||
self.image("")
|
||||
self.assertIsNone(self.made._is_ours(1234))
|
||||
|
||||
|
||||
class Machine(Local):
|
||||
"""What this machine can hold, and what that makes worth pointing at."""
|
||||
|
||||
def _sysconf(self, phys_pages, page_size=4096):
|
||||
"""Stand where sysconf answers whatever this test wants it to.
|
||||
|
||||
`create` because Windows has no os.sysconf at all, and a patch that
|
||||
insists on the real attribute fails there before the test runs. What
|
||||
the code under test does about that absence is two lines down from
|
||||
what these are checking, and it is checked on its own below.
|
||||
"""
|
||||
return mock.patch.object(
|
||||
ggml.os, "sysconf", create=True,
|
||||
side_effect=lambda name: (page_size if name == "SC_PAGE_SIZE"
|
||||
else phys_pages))
|
||||
|
||||
def test_the_memory_is_read_the_way_each_system_reports_it(self):
|
||||
# Linux and most Macs answer through sysconf.
|
||||
with self._sysconf(4_194_304):
|
||||
self.assertEqual(ggml.total_memory(), 16 * ggml.GB)
|
||||
|
||||
def test_a_mac_without_the_page_count_is_asked_for_the_number(self):
|
||||
# Not every build of Python on a Mac carries SC_PHYS_PAGES, and a Mac
|
||||
# that answered nothing would be a Mac with none of this on it.
|
||||
def answer(args, **kwargs):
|
||||
self.assertEqual(args, ["sysctl", "-n", "hw.memsize"])
|
||||
return mock.Mock(stdout=f"{32 * ggml.GB}\n")
|
||||
|
||||
with mock.patch.object(ggml.os, "sysconf", create=True,
|
||||
side_effect=ValueError), \
|
||||
mock.patch.object(sys, "platform", "darwin"), \
|
||||
mock.patch.object(ggml.subprocess, "run", answer):
|
||||
self.assertEqual(ggml.total_memory(), 32 * ggml.GB)
|
||||
|
||||
def test_a_sysconf_that_shrugs_is_an_unknown_machine_and_not_a_tiny_one(self):
|
||||
# sysconf answers -1 for a limit it holds to be indeterminate and
|
||||
# CPython hands that back rather than raising, so the product came out
|
||||
# negative: a 64 GB workstation was told every model past 512 MB was
|
||||
# too big for it, and the machine line read "Memory: -4096 B".
|
||||
with self._sysconf(-1):
|
||||
self.assertEqual(ggml.total_memory(), 0)
|
||||
self.assertTrue(ggml.fits(574 << 20, memory=0))
|
||||
|
||||
def test_the_memory_is_read_once_and_kept(self):
|
||||
# A list of thirty rows asks seventy times, and on the Mac path the
|
||||
# answer comes from a program rather than a library call.
|
||||
calls = []
|
||||
with mock.patch.object(ggml, "_read_memory",
|
||||
lambda: calls.append(1) or 16 * ggml.GB):
|
||||
self.assertEqual(ggml.total_memory(), 16 * ggml.GB)
|
||||
self.assertEqual(ggml.total_memory(), 16 * ggml.GB)
|
||||
self.assertEqual(len(calls), 1)
|
||||
|
||||
def test_a_system_that_answers_nothing_is_an_unknown_machine(self):
|
||||
with mock.patch.object(ggml.os, "sysconf", create=True,
|
||||
side_effect=ValueError), \
|
||||
mock.patch.object(sys, "platform", "linux"):
|
||||
self.assertEqual(ggml.total_memory(), 0)
|
||||
|
||||
def test_a_mac_is_taken_to_have_a_graphics_interface(self):
|
||||
with mock.patch.object(sys, "platform", "darwin"):
|
||||
self.assertEqual(ggml.accelerator(), "Metal")
|
||||
|
||||
def test_elsewhere_the_vulkan_loader_is_what_says_so(self):
|
||||
with mock.patch.object(sys, "platform", "linux"), \
|
||||
mock.patch.object(ggml.ctypes.util, "find_library",
|
||||
lambda name: "/usr/lib/libvulkan.so.1"):
|
||||
self.assertEqual(ggml.accelerator(), "Vulkan")
|
||||
with mock.patch.object(sys, "platform", "linux"), \
|
||||
mock.patch.object(ggml.ctypes.util, "find_library",
|
||||
lambda name: None):
|
||||
self.assertEqual(ggml.accelerator(), "")
|
||||
|
||||
def test_a_model_is_measured_against_half_the_memory(self):
|
||||
self.assertTrue(ggml.fits(2 * ggml.GB, memory=8 * ggml.GB))
|
||||
self.assertFalse(ggml.fits(4 * ggml.GB, memory=8 * ggml.GB))
|
||||
|
||||
def test_a_machine_whose_memory_could_not_be_read_holds_anything(self):
|
||||
# A wrong "too big" is worse advice than none.
|
||||
self.assertTrue(ggml.fits(40 * ggml.GB, memory=0))
|
||||
|
||||
def test_the_smallest_machine_is_not_the_one_where_everything_fits(self):
|
||||
# Half of 2 GB less the gigabyte of overhead is nothing, and a budget
|
||||
# of nothing used to read as the unknown machine above.
|
||||
self.assertFalse(ggml.fits(3 * ggml.GB, memory=2 * ggml.GB))
|
||||
|
||||
def test_a_crowded_machine_is_pointed_at_the_smaller_model(self):
|
||||
self.assertEqual(ggml.suggested_whisper(memory=3 * ggml.GB, graphics=""),
|
||||
ggml.SMALL_MACHINE_WHISPER)
|
||||
|
||||
def test_a_card_and_the_memory_for_it_are_pointed_at_the_accurate_one(self):
|
||||
self.assertEqual(
|
||||
ggml.suggested_whisper(memory=32 * ggml.GB, graphics="Vulkan"),
|
||||
ggml.ACCURATE_WHISPER)
|
||||
|
||||
def test_memory_without_a_card_is_pointed_at_the_fast_one(self):
|
||||
# Several times the work per second is several times a long wait on a
|
||||
# processor, whatever there is room for.
|
||||
self.assertEqual(
|
||||
ggml.suggested_whisper(memory=32 * ggml.GB, graphics=""),
|
||||
ggml.SUGGESTED_WHISPER)
|
||||
|
||||
def test_a_sixteen_gigabyte_machine_counts_as_a_roomy_one(self):
|
||||
# What a machine reports is what the firmware and the graphics left
|
||||
# of it: 16 GB answers about 15.4, and a threshold written at the
|
||||
# number on the box is one no machine ever reaches.
|
||||
self.assertEqual(
|
||||
ggml.suggested_whisper(memory=int(15.4 * ggml.GB), graphics="Metal"),
|
||||
ggml.ACCURATE_WHISPER)
|
||||
|
||||
def test_the_suggestion_that_fits_is_offered_first(self):
|
||||
first = ggml.suggested_llm(memory=6 * ggml.GB)[0]
|
||||
self.assertTrue(ggml.fits(ggml.SUGGESTED_LLM_SIZE[first],
|
||||
memory=6 * ggml.GB))
|
||||
# Nothing is dropped: what does not fit today fits once something else
|
||||
# is closed.
|
||||
self.assertEqual(sorted(ggml.suggested_llm(memory=6 * ggml.GB)),
|
||||
sorted(ggml.SUGGESTED_LLM))
|
||||
|
||||
def test_the_wanted_model_wins_when_there_is_room_for_it(self):
|
||||
items = [listed("ggml-tiny.bin", 70 << 20),
|
||||
listed("ggml-large-v3-turbo-q5_0.bin", 574 << 20)]
|
||||
self.assertEqual(
|
||||
ggml.recommended(items, "ggml-large-v3-turbo-q5_0.bin",
|
||||
memory=16 * ggml.GB),
|
||||
"ggml-large-v3-turbo-q5_0.bin")
|
||||
|
||||
def test_a_model_too_big_for_the_machine_is_not_recommended(self):
|
||||
items = [listed("small.gguf", 1 << 30), listed("huge.gguf", 12 * ggml.GB)]
|
||||
self.assertEqual(ggml.recommended(items, "huge.gguf",
|
||||
memory=8 * ggml.GB), "small.gguf")
|
||||
|
||||
def test_the_full_precision_weights_are_never_the_recommendation(self):
|
||||
# Twice the memory and twice the wait for a difference this job
|
||||
# cannot see.
|
||||
items = [listed("model-Q4_0.gguf", 2 * ggml.GB),
|
||||
listed("model-BF16.gguf", 3 * ggml.GB)]
|
||||
self.assertEqual(ggml.recommended(items, memory=32 * ggml.GB),
|
||||
"model-Q4_0.gguf")
|
||||
|
||||
def test_nothing_is_recommended_when_nothing_fits(self):
|
||||
self.assertEqual(
|
||||
ggml.recommended([listed("huge.gguf", 40 * ggml.GB)],
|
||||
memory=8 * ggml.GB), "")
|
||||
|
||||
|
||||
class Grouping(Local):
|
||||
"""One group per model, rather than one long list sorted by size."""
|
||||
|
||||
def test_every_spelling_of_a_quantisation_reads_as_its_number(self):
|
||||
# One list holds q5_1, Q4_K_M, MXFP4 and BF16, and the number is the
|
||||
# whole of what any of them says to somebody choosing a row.
|
||||
self.assertEqual(ggml.bit_depth("ggml-small-q5_1.bin"), 5)
|
||||
self.assertEqual(ggml.bit_depth("SmolLM3-Q4_K_M.gguf"), 4)
|
||||
self.assertEqual(ggml.bit_depth("gpt-oss-20b-MXFP4.gguf"), 4)
|
||||
self.assertEqual(ggml.bit_depth("gemma-4-E2B-it-Q8_0.gguf"), 8)
|
||||
# bf16 is not f16 read badly.
|
||||
self.assertEqual(ggml.bit_depth("gemma-4-E2B-it-BF16.gguf"), 16)
|
||||
self.assertEqual(ggml.bit_depth("mmproj-model-f16.gguf"), 16)
|
||||
# A whisper file with no mark is the full model, and its name is the
|
||||
# one convention here that does not carry the answer.
|
||||
self.assertEqual(ggml.bit_depth("ggml-large-v3-turbo.bin"), 0)
|
||||
|
||||
def test_a_quantisation_belongs_to_the_model_it_is_a_copy_of(self):
|
||||
self.assertEqual(ggml.whisper_family("ggml-small.en-q5_1.bin"), "small")
|
||||
self.assertEqual(ggml.whisper_family("ggml-large-v3-q5_0.bin"),
|
||||
"large-v3")
|
||||
self.assertEqual(ggml.whisper_family("ggml-large-v3-turbo.bin"),
|
||||
"large-v3-turbo")
|
||||
self.assertEqual(ggml.whisper_family("ggml-medium.en.bin"), "medium")
|
||||
|
||||
def test_turbo_is_a_model_and_not_a_quantisation(self):
|
||||
# The last chunk of the name is a quantisation for most of the list
|
||||
# and part of the model's name here.
|
||||
self.assertEqual(ggml.whisper_family("ggml-large-v3-turbo-q8_0.bin"),
|
||||
"large-v3-turbo")
|
||||
|
||||
def test_the_turbo_files_are_not_scattered_through_the_medium_ones(self):
|
||||
# Sorted by size alone, large-v3-turbo-q5_0 lands between the two
|
||||
# medium quantisations, half a screen from the model it is a copy of.
|
||||
models = [listed("ggml-medium-q5_0.bin", 539 << 20),
|
||||
listed("ggml-large-v3-turbo-q5_0.bin", 574 << 20),
|
||||
listed("ggml-medium-q8_0.bin", 823 << 20),
|
||||
listed("ggml-large-v3-turbo.bin", 1624 << 20)]
|
||||
groups = dict(ggml.whisper_groups(models))
|
||||
self.assertEqual([i.name for i in groups["large-v3-turbo"]],
|
||||
["ggml-large-v3-turbo-q5_0.bin",
|
||||
"ggml-large-v3-turbo.bin"])
|
||||
self.assertEqual([i.name for i in groups["medium"]],
|
||||
["ggml-medium-q5_0.bin", "ggml-medium-q8_0.bin"])
|
||||
|
||||
def test_the_smallest_model_comes_first_and_the_english_ones_last(self):
|
||||
models = [listed("ggml-small.en-q5_1.bin", 190 << 20),
|
||||
listed("ggml-small-q5_1.bin", 190 << 20),
|
||||
listed("ggml-tiny.bin", 77 << 20)]
|
||||
groups = ggml.whisper_groups(models)
|
||||
self.assertEqual([family for family, _ in groups], ["tiny", "small"])
|
||||
self.assertEqual([i.name for _, group in groups for i in group],
|
||||
["ggml-tiny.bin", "ggml-small-q5_1.bin",
|
||||
"ggml-small.en-q5_1.bin"])
|
||||
|
||||
+197
-1
@@ -1352,6 +1352,35 @@ class LocalModels(DikteTest):
|
||||
def _item(name, size=1 << 20):
|
||||
return hub.Item(name, f"https://example.invalid/{name}", size, "")
|
||||
|
||||
@staticmethod
|
||||
def _rows(box):
|
||||
"""Every row's text, headings included."""
|
||||
return [box.model.itemText(row) for row in range(box.model.count())]
|
||||
|
||||
@staticmethod
|
||||
def _repos(box):
|
||||
return [box.repo.itemText(row) for row in range(box.repo.count())]
|
||||
|
||||
@staticmethod
|
||||
def _roomy():
|
||||
"""Stand on a machine with room for every suggestion.
|
||||
|
||||
The order the publishers come in follows the memory, so a test that
|
||||
reads it has to say which machine it is standing on. A build runner
|
||||
with 7 GB in it puts the two Gemma 4 rows last and is right to.
|
||||
"""
|
||||
return mock.patch.object(ggml, "total_memory", return_value=64 << 30)
|
||||
|
||||
@staticmethod
|
||||
def _offered(box):
|
||||
"""The model names in the box, headings and duplicates left out."""
|
||||
names = []
|
||||
for row in range(box.model.count()):
|
||||
name = box.model.itemData(row)
|
||||
if name and name not in names:
|
||||
names.append(name)
|
||||
return names
|
||||
|
||||
def test_a_row_with_nothing_to_fetch_does_not_offer_a_download(self):
|
||||
# The model the settings name is not in the list any more, so its row
|
||||
# was rebuilt from the name alone and carries no file to fetch. The
|
||||
@@ -1388,7 +1417,7 @@ class LocalModels(DikteTest):
|
||||
box._on_listed([("models", [self._item("SmolLM3-Q4_K_M.gguf")],
|
||||
"ggml-org/SmolLM3-3B-GGUF")], "")
|
||||
self.assertEqual(box.selected(), "SmolLM3-Q4_K_M.gguf")
|
||||
self.assertEqual(box.model.count(), 1)
|
||||
self.assertEqual(self._offered(box), ["SmolLM3-Q4_K_M.gguf"])
|
||||
|
||||
def test_a_list_for_a_publisher_that_is_no_longer_chosen_is_dropped(self):
|
||||
# Every change starts its own request, and they do not come back in the
|
||||
@@ -1416,6 +1445,173 @@ class LocalModels(DikteTest):
|
||||
time.sleep(0.05)
|
||||
_app.processEvents()
|
||||
self.assertEqual(fetch.call_count, 1)
|
||||
def test_the_models_are_grouped_by_the_model_rather_than_by_size(self):
|
||||
# Sorted by size alone, the turbo files land between the two medium
|
||||
# ones, half a screen from the model they are a copy of.
|
||||
box = self.window(cfg.Config()).local_whisper
|
||||
with mock.patch.object(ggml, "total_memory", return_value=8 << 30), \
|
||||
mock.patch.object(ggml, "accelerator", return_value=""):
|
||||
box._on_listed([("models", [
|
||||
self._item("ggml-medium-q5_0.bin", 539 << 20),
|
||||
self._item("ggml-large-v3-turbo-q5_0.bin", 574 << 20),
|
||||
self._item("ggml-medium-q8_0.bin", 823 << 20),
|
||||
self._item("ggml-large-v3-turbo.bin", 1624 << 20),
|
||||
], "")], "")
|
||||
rows = self._rows(box)
|
||||
# The two medium files under one heading, the two turbo ones under
|
||||
# theirs, and the model rather than the file deciding the order.
|
||||
self.assertEqual(rows[rows.index("medium"):],
|
||||
["medium",
|
||||
"ggml-medium-q5_0.bin (539.0 MB, 5-bit)",
|
||||
"ggml-medium-q8_0.bin (823.0 MB, 8-bit)",
|
||||
"large-v3-turbo",
|
||||
"ggml-large-v3-turbo-q5_0.bin "
|
||||
"(574.0 MB, 5-bit, recommended)",
|
||||
"ggml-large-v3-turbo.bin (1.6 GB, 16-bit)"])
|
||||
# A heading is not a model, and nothing can be saved from one.
|
||||
self.assertIsNone(box.model.itemData(rows.index("medium")))
|
||||
|
||||
def test_the_row_for_this_machine_is_on_top_and_says_so(self):
|
||||
box = self.window(cfg.Config()).local_whisper
|
||||
with mock.patch.object(ggml, "total_memory", return_value=8 << 30), \
|
||||
mock.patch.object(ggml, "accelerator", return_value=""):
|
||||
box._on_listed([("models", [
|
||||
self._item("ggml-tiny.bin", 77 << 20),
|
||||
self._item("ggml-large-v3-turbo-q5_0.bin", 574 << 20),
|
||||
], "")], "")
|
||||
self.assertEqual(box.selected(), "ggml-large-v3-turbo-q5_0.bin")
|
||||
self.assertEqual(box.model.itemData(1), "ggml-large-v3-turbo-q5_0.bin")
|
||||
self.assertIn(t("recommended"), box.model.itemText(1))
|
||||
|
||||
def test_a_model_the_memory_cannot_hold_says_so_on_its_row(self):
|
||||
box = self.window(cfg.Config()).local_llm
|
||||
box.repo.blockSignals(True)
|
||||
box.repo.setCurrentText("ggml-org/x-GGUF")
|
||||
box.repo.blockSignals(False)
|
||||
with mock.patch.object(ggml, "total_memory", return_value=8 << 30):
|
||||
box._on_listed([("models", [
|
||||
self._item("small-Q4_0.gguf", 1 << 30),
|
||||
self._item("huge-Q8_0.gguf", 12 << 30),
|
||||
], "ggml-org/x-GGUF")], "")
|
||||
rows = {box.model.itemData(row): box.model.itemText(row)
|
||||
for row in range(box.model.count())}
|
||||
self.assertNotIn(t("too big for this machine"), rows["small-Q4_0.gguf"])
|
||||
self.assertIn(t("too big for this machine"), rows["huge-Q8_0.gguf"])
|
||||
|
||||
def test_a_recommended_row_is_not_listed_twice_after_a_download(self):
|
||||
# It has a row of its own on top as well as one in its group, and
|
||||
# reading the rows back the way a finished download does was doubling
|
||||
# it in the list every time.
|
||||
box = self.window(cfg.Config()).local_whisper
|
||||
with self._roomy():
|
||||
box._on_listed([("models", [
|
||||
self._item("ggml-tiny.bin", 77 << 20),
|
||||
self._item("ggml-large-v3-turbo-q5_0.bin", 574 << 20),
|
||||
], "")], "")
|
||||
before = self._offered(box)
|
||||
box._fill_models_from_current()
|
||||
self.assertEqual(self._offered(box), before)
|
||||
names = [box.model.itemData(row) for row in range(box.model.count())]
|
||||
self.assertEqual(len([n for n in names if n]), len(before) + 1)
|
||||
|
||||
def test_a_processor_build_is_not_recommended_the_accurate_model(self):
|
||||
# The Vulkan loader is on the machine but what was installed is the
|
||||
# processor build, so there is no card in play whatever the loader
|
||||
# says, and a 1 GB model on a processor is a wait somebody is sitting
|
||||
# through with a sentence half typed.
|
||||
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"}))
|
||||
self.patch_attr(ggml.shutil, "which", lambda name: None)
|
||||
box = self.window(cfg.Config()).local_whisper
|
||||
with mock.patch.object(ggml, "total_memory", return_value=32 << 30), \
|
||||
mock.patch.object(ggml, "accelerator", return_value="Vulkan"):
|
||||
self.assertEqual(box._suggested(), ggml.SUGGESTED_WHISPER)
|
||||
|
||||
def test_a_publisher_with_nothing_to_offer_says_why(self):
|
||||
# Half of what ggml-org publishes is split across files or past the
|
||||
# size cap, and an empty box read as though the click had not landed.
|
||||
box = self.window(cfg.Config()).local_llm
|
||||
box.repo.blockSignals(True)
|
||||
box.repo.setCurrentText("ggml-org/gpt-oss-120b-GGUF")
|
||||
box.repo.blockSignals(False)
|
||||
box._on_listed([("models", [], "ggml-org/gpt-oss-120b-GGUF")], "")
|
||||
self.assertIn("ggml-org/gpt-oss-120b-GGUF", box.status.text())
|
||||
self.assertIn("publisher", box.status.text())
|
||||
|
||||
def test_an_empty_box_nobody_has_asked_yet_is_not_a_publisher_fault(self):
|
||||
box = self.window(cfg.Config()).local_llm
|
||||
box.load("", "ggml-org/SmolLM3-3B-GGUF")
|
||||
self.assertNotIn("publisher", box.status.text())
|
||||
|
||||
def test_only_the_suggested_publishers_are_offered_to_start_with(self):
|
||||
# Forty repository ids is not a choice anybody can make.
|
||||
box = self.window(cfg.Config()).local_llm
|
||||
with self._roomy():
|
||||
box._on_listed([("repos", [ggml.SUGGESTED_LLM[0],
|
||||
"ggml-org/something-else-GGUF"], "")], "")
|
||||
self.assertEqual(self._repos(box), list(ggml.SUGGESTED_LLM))
|
||||
|
||||
def test_a_suggestion_missing_from_the_listing_is_still_offered(self):
|
||||
# The listing is the forty repositories touched most recently, and a
|
||||
# publisher that has not been updated in a season falls off it while
|
||||
# still being the one to point at.
|
||||
box = self.window(cfg.Config()).local_llm
|
||||
box._on_listed([("repos", ["ggml-org/something-else-GGUF"], "")], "")
|
||||
self.assertIn(ggml.SUGGESTED_LLM[0], self._repos(box))
|
||||
|
||||
def test_the_switch_brings_the_rest_and_keeps_them_apart(self):
|
||||
box = self.window(cfg.Config()).local_llm
|
||||
with self._roomy():
|
||||
box._on_listed([("repos", [ggml.SUGGESTED_LLM[0],
|
||||
"ggml-org/something-else-GGUF"], "")], "")
|
||||
box.every_repo.setChecked(True)
|
||||
rows = self._repos(box)
|
||||
self.assertEqual(rows[:len(ggml.SUGGESTED_LLM)],
|
||||
list(ggml.SUGGESTED_LLM))
|
||||
# A separator rather than a heading: the box is typed into as well as
|
||||
# chosen from, and a heading would land in the field as a repository.
|
||||
self.assertEqual(rows[len(ggml.SUGGESTED_LLM)], "")
|
||||
self.assertEqual(rows[-1], "ggml-org/something-else-GGUF")
|
||||
|
||||
def test_a_publisher_typed_in_is_not_dropped_by_the_next_fetch(self):
|
||||
box = self.window(cfg.Config()).local_llm
|
||||
box.repo.blockSignals(True)
|
||||
box.repo.setCurrentText("ggml-org/something-else-GGUF")
|
||||
box.repo.blockSignals(False)
|
||||
box._on_listed([("repos", [ggml.SUGGESTED_LLM[0],
|
||||
"ggml-org/something-else-GGUF"], "")], "")
|
||||
self.assertFalse(box.every_repo.isChecked())
|
||||
self.assertIn("ggml-org/something-else-GGUF", self._repos(box))
|
||||
self.assertEqual(box.repository(), "ggml-org/something-else-GGUF")
|
||||
|
||||
def test_the_chosen_publisher_is_said_in_words(self):
|
||||
# A repository id names the publisher, the parameter count and the
|
||||
# shape of the weights, and none of that says whether to click it.
|
||||
box = self.window(cfg.Config()).local_llm
|
||||
box.repo.setCurrentText(ggml.SUGGESTED_LLM[0])
|
||||
self.assertTrue(box.repo_note.text())
|
||||
box.repo.setCurrentText("ggml-org/nobody-wrote-a-note-GGUF")
|
||||
self.assertEqual(box.repo_note.text(), "")
|
||||
|
||||
def test_the_box_says_what_this_machine_will_run_on(self):
|
||||
box = self.window(cfg.Config()).local_whisper
|
||||
with mock.patch.object(ggml, "accelerator", return_value="Vulkan"), \
|
||||
mock.patch.object(ggml, "total_memory", return_value=32 << 30):
|
||||
box._show_machine()
|
||||
self.assertIn("Vulkan", box.machine_label.text())
|
||||
self.assertIn("32.0 GB", box.machine_label.text())
|
||||
|
||||
def test_a_machine_with_no_card_is_told_it_is_on_the_processor(self):
|
||||
box = self.window(cfg.Config()).local_whisper
|
||||
with mock.patch.object(ggml, "accelerator", return_value=""), \
|
||||
mock.patch.object(ggml, "total_memory", return_value=8 << 30):
|
||||
box._show_machine()
|
||||
self.assertIn("processor", box.machine_label.text())
|
||||
|
||||
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,
|
||||
|
||||
Reference in New Issue
Block a user