Resolve the override key on save and match standalone gguf settings

The remove branch already resolved the key a load would use, but the save
branch wrote payload.model_id literally. The browser normalizes casing before
storing, so a backfilled key and a later UI save left two entries for one
model; with two equivalent keys present resolve_model_override_key finds no
unique match, so any third casing resolved to no override at all and the model
silently loaded with defaults.

A standalone .gguf gets variants=() from the resolver, so variant is None and
only the bare ids were tried. The picker keys the same file by the quant label
it derives from the filename, which is never empty, so those settings lived
under <path>:LABEL and nothing reached them. Try the filename-derived key after
the variant-qualified ones and before the bare ones, so older bare entries
still work.
This commit is contained in:
Daniel Han 2026-07-28 13:16:39 +00:00
commit f39fd0e408
3 changed files with 38 additions and 1 deletions

View file

@ -4291,10 +4291,22 @@ async def _maybe_auto_switch_model(
# different configs), then the bare ids. Both the advertised
# repo id and the concrete load path are tried: a local folder
# or a non-active HF cache is configured against its path.
# A standalone .gguf needs no quant sub-selection, so the
# resolver reports variant=None for it. The picker still
# keys its config by the quant label it derives from the
# filename (LocalModelInfo.format_variant), which is never
# empty, so those settings live under "<path>:LABEL" and no
# bare key would ever reach them.
file_variant = None
if not variant and target_id.lower().endswith(".gguf"):
from hub.utils.gguf import extract_quant_label
file_variant = extract_quant_label(os.path.basename(target_id))
override = {}
for override_key in (
f"{override_id}:{variant}" if variant else None,
f"{target_id}:{variant}" if variant else None,
f"{target_id}:{file_variant}" if file_variant else None,
override_id,
target_id,
):

View file

@ -411,8 +411,14 @@ def update_openai_auto_switch_override(
target_id = resolve_model_override_key(payload.model_id) or payload.model_id
set_model_override(target_id, llama_extra_args = [], max_seq_length = None)
else:
# Save under the key a load would resolve to, for the same reason the
# removal branch does. The browser normalizes casing before storing,
# so saving the literal id leaves a second entry for one model, and
# two equivalent keys make every other casing ambiguous: the lookup
# then matches neither and the model silently loses its settings.
target_id = resolve_model_override_key(payload.model_id) or payload.model_id
set_model_override(
payload.model_id,
target_id,
llama_extra_args = extra_args,
max_seq_length = payload.max_seq_length,
custom_context_length = payload.custom_context_length,

View file

@ -4748,6 +4748,25 @@ def test_removal_clears_the_entry_a_load_would_actually_resolve(monkeypatch):
assert settings.get_model_override("unsloth/B-GGUF:Q4_K_M") == {}
def test_save_updates_the_existing_case_variant_instead_of_forking_it(monkeypatch):
# The backfill stores normalized (lowercase) keys while a later UI save carries
# the catalog's casing. Writing that literally leaves two keys for one model,
# and with two equivalent keys present any third casing resolves ambiguously,
# so the model silently loses every saved setting on the API path.
import routes.settings as settings_route
_mock_override_store(monkeypatch)
settings.set_model_override("unsloth/b-gguf:q4_k_m", max_seq_length = 8192)
settings_route.update_openai_auto_switch_override(
settings_route.ModelOverridePayload(
model_id = "unsloth/B-GGUF:Q4_K_M", max_seq_length = 4096
),
"tester",
)
assert list(settings.get_model_overrides()) == ["unsloth/b-gguf:q4_k_m"]
assert settings.get_model_override("Unsloth/B-GGUF:Q4_K_M")["max_seq_length"] == 4096
def test_removal_of_a_path_still_only_touches_the_exact_key(monkeypatch):
import routes.settings as settings_route