config(multi-config): fix GGUF dispatch + per-config-id measured persistence

Follow-up to the same-path multi-config runtime fix. Two more path-keyed spots
broke a same-file sibling addressed by its alias (e.g. lisa-32k):

1) GGUF dispatch: _resolve_local_gguf matched entries by path/basename only, so
   a sibling addressed by alias wasn't recognized as GGUF and fell to the HF
   loader ("lisa-32k is not a valid model identifier"). Now also match the
   entry's alias.

2) Measured footprint cross-contamination: persist_model_field matched entries
   by path, so one config's measured_vram_gb/n_gpu_layers overwrote the sibling's
   — the small-ctx variant inherited the large-ctx footprint (34 GB) and still
   offloaded to CPU. persist_model_field now targets by config_id when given;
   record_vram_delta passes cfg['config_id']; _model_cfg carries config_id/
   config_name so the config knows its own entry identity.

Net: lisa (178K) and lisa-32k (32K) are now fully independent — distinct config,
GGUF load from the shared file, and separate measured footprints.
Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mw2KQiswmD69T45fTfjKwW
parent 6ec4af1c
......@@ -1180,7 +1180,7 @@ class ConfigManager:
with open(self.models_path, 'w') as f:
json.dump(self.models_data, f, indent=2)
def persist_model_field(self, model_path: str, key: str, value) -> bool:
def persist_model_field(self, model_path: str, key: str, value, config_id: str = None) -> bool:
"""Set a SINGLE field on the matching model entry by RE-READING models.json
from disk first, then writing back — never dumping this process's whole
in-memory models_data.
......@@ -1207,11 +1207,21 @@ class ConfigManager:
for entry in lst:
if not isinstance(entry, dict):
continue
epath = entry.get("path") or entry.get("id") or ""
if epath == bare or epath.split("/")[-1] == bare.split("/")[-1]:
if entry.get(key) != value:
entry[key] = value
changed = True
# When a config_id is given, target ONLY that exact entry — so a
# same-path sibling config (multi-config) doesn't inherit the other's
# measured footprint (which would make e.g. a small-ctx variant offload
# using the large-ctx variant's VRAM figure). Fall back to path/basename
# matching for legacy entries without a config_id.
if config_id:
if entry.get("config_id") != config_id:
continue
else:
epath = entry.get("path") or entry.get("id") or ""
if not (epath == bare or epath.split("/")[-1] == bare.split("/")[-1]):
continue
if entry.get(key) != value:
entry[key] = value
changed = True
if changed:
tmp = str(self.models_path) + ".tmp"
with open(tmp, 'w') as f:
......
......@@ -897,7 +897,10 @@ def main():
def _model_cfg(m, mtype):
cfg = build_kwargs_from_config(m, mtype) if isinstance(m, dict) else {}
if isinstance(m, dict):
for k in ("load_mode", "used_vram_gb", "alias", "max_instances"):
# config_id/config_name carry the entry's identity so runtime-measured
# fields persist back to the EXACT entry (multi-config siblings share a path).
for k in ("load_mode", "used_vram_gb", "alias", "max_instances",
"config_id", "config_name"):
if k in m:
cfg[k] = m[k]
return cfg
......
......@@ -124,11 +124,21 @@ def _resolve_local_gguf(model_name: str):
if not isinstance(lst, list):
continue
for m in lst:
key = m if isinstance(m, str) else (m.get("path") or m.get("id") or "") if isinstance(m, dict) else ""
if isinstance(m, dict):
key = m.get("path") or m.get("id") or ""
_alias = m.get("alias") or ""
else:
key = m if isinstance(m, str) else ""
_alias = ""
if not key or not str(key).lower().endswith(".gguf"):
continue
kb = os.path.basename(key)
if (key == model_name or kb == base or kb[:-5] == base_noext) and os.path.isfile(os.path.expanduser(key)):
# Match by path / basename, OR by the entry's ALIAS — a same-file
# sibling config (multi-config) is addressed by its alias, which
# won't match the shared path/basename.
if (key == model_name or kb == base or kb[:-5] == base_noext
or (_alias and _alias == model_name)) \
and os.path.isfile(os.path.expanduser(key)):
return os.path.expanduser(key)
except Exception:
pass
......@@ -2991,7 +3001,10 @@ class MultiModelManager:
self.config[canon_key] = dict(working)
from codai.admin.routes import config_manager
if config_manager is not None:
config_manager.persist_model_field(canon_key, field, value)
# Target the exact entry by config_id so same-path sibling configs
# (multi-config) don't cross-contaminate each other's measured fields.
config_manager.persist_model_field(
canon_key, field, value, config_id=cfgd.get("config_id"))
print(f" Saved {field}={value} for '{model_key}' "
f"({'force_vram_update' if force_update else 'no used_vram_gb'})")
except Exception as e:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment