Stop the adapter report from claiming things that are not true

Independent review of the adapter resolution work found six defects, all
real and all fixed here.

A kind whose only candidates were rejected had no winning path, so its row
carried path=None and the rejected candidate fell into the shadow loop: the
report said "another adapter of this kind wins for the current mount" while
nothing won, and the real deferred reason was buried. A shadow row now
requires a winner and names it.

"loaded" was backed by a process-global record, so a fresh runtime with no
model still claimed a live adapter. The record is now held by the instance
that attached it, and the report keeps a weak reference: while the model
lives the row says loaded, once it is gone the row says available and
explains that the model is gone.

The sort key had lost the manifest's cartridge tag, which changed which
adapter loads on any machine holding a tagged library copy, and neither the
commit message nor the checklist said so. The tag is back as the middle key.

Proof checks resolved through the library while generation resolved through
the mount, so one proof payload could name two different copies. Both now
resolve the way generation does.

loaded_at was written and never read, so it is gone. The checklist claim that
peft is lazily imported in one place was true only for generation and is now
scoped that way.

The four generation paths and status() had no test coverage, so "no function
was lost" rested on reading. One test now walks the transformers prompt path
end to end with stub weights and asserts the generation answer carries the
adapter it attached.

pytest -q: 225 passed. ruff clean. mypy on the three touched source files:
2 errors, the identical 2 on a stashed HEAD tree, net zero. pyright: 2
errors, both the known qwen_vl_utils tuple artifact, unchanged from HEAD.
This commit is contained in:
ZacharyZhang-NY committed 2026-10-08 00:03:27 -04:00
1 parent 2fbb4af96b
commit 2d1cfdadc7
5 files changed
+128 -38

No files matched your search

+31 -20
View File
@@ -3,8 +3,8 @@
from __future__ import annotations
import logging
import weakref
from dataclasses import dataclass
from datetime import datetime
from pathlib import Path
from typing import Any
@@ -34,16 +34,16 @@ class AdapterSource:
@dataclass(frozen=True)
class LoadRecord:
"""An adapter this process actually attached to a live model."""
"""An adapter this process attached to a model."""
kind: TrainingAdapterKind
adapter_id: str
path: str
sha256: str
loaded_at: str
_LOADED: dict[str, LoadRecord] = {}
_LIVE: dict[str, weakref.ReferenceType[object]] = {}
def read_manifest(path: Path) -> AdapterManifest:
@@ -143,36 +143,41 @@ def resolve(
return chosen, "available", "selected from the adapter library"
def resolve_library_adapter(
settings: Settings,
kind: TrainingAdapterKind = TrainingAdapterKind.hme_core_lora,
) -> tuple[AdapterSource | None, AdapterState, str]:
"""Resolution without a database: the library only, no mounted cartridges."""
return resolve(library_sources(settings), settings, kind)
def _preference(source: AdapterSource) -> tuple[bool, float]:
def _preference(source: AdapterSource) -> tuple[bool, bool, float]:
weights = source.path / WEIGHTS_FILE
return (source.origin == "cartridge", weights.stat().st_mtime if weights.exists() else 0.0)
return (
source.origin == "cartridge",
source.manifest.cartridge_id is not None,
weights.stat().st_mtime if weights.exists() else 0.0,
)
def load_records() -> list[LoadRecord]:
return list(_LOADED.values())
def live_kinds() -> set[str]:
"""Kinds whose attached model is still alive, not merely attached once."""
alive = {kind for kind, reference in _LIVE.items() if reference() is not None}
for kind in [kind for kind, reference in _LIVE.items() if reference() is None]:
del _LIVE[kind]
return alive
def clear_loads() -> None:
_LOADED.clear()
_LIVE.clear()
def record_load(source: AdapterSource) -> LoadRecord:
def record_load(source: AdapterSource, model: Any) -> LoadRecord:
record = LoadRecord(
kind=source.manifest.kind,
adapter_id=source.manifest.adapter_id,
path=str(source.path),
sha256=source.manifest.sha256,
loaded_at=datetime.now().astimezone().isoformat(),
)
_LOADED[record.kind.value] = record
_LIVE[record.kind.value] = weakref.ref(model)
logger.info("loaded adapter %s (%s) from %s", record.adapter_id, record.kind.value, record.path)
return record
@@ -191,12 +196,18 @@ def plan(settings: Settings, database: Database) -> AdapterLoadReport:
"""One row per adapter kind, plus a row for every adapter that lost the choice."""
sources, mounted = discover(settings, database)
rows: list[AdapterChoice] = []
winners: dict[str, AdapterSource | None] = {}
alive = live_kinds()
for kind in TrainingAdapterKind:
source, state, reason = resolve(sources, settings, kind)
winners[kind.value] = source
record = next((item for item in load_records() if item.kind == kind), None)
if record is not None:
reason = f"attached to the live model from {record.path}"
state = "loaded"
if kind.value in alive:
state, reason = "loaded", f"attached to the live model from {record.path}"
else:
state = "available"
reason = f"attached earlier in this process from {record.path}; that model is gone"
rows.append(
AdapterChoice(
kind=kind,
@@ -209,15 +220,15 @@ def plan(settings: Settings, database: Database) -> AdapterLoadReport:
cartridge_id=source.cartridge_id if source else None,
)
)
chosen_paths = {row.path for row in rows if row.path}
for source in sources:
if str(source.path) in chosen_paths:
winner = winners.get(source.manifest.kind.value)
if winner is None or source.path == winner.path:
continue
rows.append(
AdapterChoice(
kind=source.manifest.kind,
state="deferred",
reason="another adapter of this kind wins for the current mount",
reason=f"shadowed by {winner.manifest.adapter_id} for the current mount",
path=str(source.path),
adapter_id=source.manifest.adapter_id,
base_model=source.manifest.base_model,
+13 -2
View File
@@ -19,6 +19,7 @@ from ..evaluation_schemas import (
from ..evidence_graph import EvidenceGraphService
from ..profiles import runtime_profile_report
from ..runtime import QwenRuntime
from ..runtime_schemas import AdapterState
from ..schemas import (
RuntimeProfile,
TrainingAdapterKind,
@@ -40,6 +41,16 @@ from .training_trace import (
)
def _generation_adapter(
settings: Settings,
database: Database,
kind: TrainingAdapterKind = TrainingAdapterKind.hme_core_lora,
) -> tuple[adapters.AdapterSource | None, AdapterState, str]:
"""Resolve the way generation does, mounted cartridges included, so proof and generation name one copy."""
sources, _mounted = adapters.discover(settings, database)
return adapters.resolve(sources, settings, kind)
class RuntimeChecks:
"""Runtime adapter binding, gold-adapter generation, launch profiles, and the removed module check."""
@@ -59,7 +70,7 @@ class RuntimeChecks:
return deferred
runtime = QwenRuntime(self.settings)
status = runtime.status()
adapter, _adapter_state, _adapter_reason = adapters.resolve_library_adapter(self.settings)
adapter, _adapter_state, _adapter_reason = _generation_adapter(self.settings, self.db)
weight_path = adapter.path / "adapter_model.safetensors" if adapter else None
payload = {
"runtime_status": status.model_dump(mode="json"),
@@ -134,7 +145,7 @@ class RuntimeChecks:
)
payload["generation_question"] = generation_question
status = runtime.status()
adapter, _adapter_state, _adapter_reason = adapters.resolve_library_adapter(proof_settings)
adapter, _adapter_state, _adapter_reason = _generation_adapter(proof_settings, self.db)
generation = runtime.generate(generation_question, context)
answer, citations, citation_report = Verifier().verify_answer(generation.text, context)
claim_id = EvidenceGraphService(proof_settings, self.db).record_answer(
+5 -3
View File
@@ -42,6 +42,7 @@ class QwenRuntime:
self.database = Database(self.settings)
self._processor: Any | None = None
self._model: Any | None = None
self._attached: adapters.LoadRecord | None = None
def _adapter_choice(self) -> tuple[adapters.AdapterSource | None, str]:
"""The adapter this runtime would attach, plus the reason if it would not attach one."""
@@ -50,7 +51,8 @@ class QwenRuntime:
return source, reason
def _loaded_adapter(self) -> adapters.LoadRecord | None:
return next((item for item in adapters.load_records() if item.kind == TrainingAdapterKind.hme_core_lora), None)
"""What this instance attached; another instance's model is not this one's claim."""
return self._attached
def _generation_adapter_fields(self) -> dict[str, str | None]:
record = self._loaded_adapter()
@@ -314,7 +316,7 @@ class QwenRuntime:
logger.info("generating without an adapter: %s", reason)
else:
model = adapters.load_into_model(model, source.path)
adapters.record_load(source)
self._attached = adapters.record_load(source, model)
model.eval()
self._model, self._processor = model, processor
return model, processor
@@ -345,7 +347,7 @@ class QwenRuntime:
def unload(self) -> None:
self._model = None
self._processor = None
adapters.clear_loads()
self._attached = None
try:
import torch
+65 -9
View File
@@ -3,12 +3,15 @@
from __future__ import annotations
import json
import sys
from datetime import UTC, datetime
from pathlib import Path
from types import SimpleNamespace
from typing import Any
from unittest import mock
import pytest
import torch
from fastapi.testclient import TestClient
from ely_eye import adapters, api
@@ -92,10 +95,25 @@ def _cartridge(settings: Settings, database: Database, cartridge_id: str) -> Pat
class _FakeModel:
def __init__(self) -> None:
self.evalled = False
self.device = "cpu"
def eval(self) -> None:
self.evalled = True
def generate(self, **_kwargs: Any) -> torch.Tensor:
return torch.zeros((1, 8), dtype=torch.long)
class _FakeProcessor:
def apply_chat_template(self, _messages: Any, **_kwargs: Any) -> str:
return "prompt"
def __call__(self, **_kwargs: Any) -> dict[str, torch.Tensor]:
return {"input_ids": torch.zeros((1, 5), dtype=torch.long)}
def batch_decode(self, _generated: Any, **_kwargs: Any) -> list[str]:
return ["three tokens"]
@pytest.fixture(autouse=True)
def _no_stale_loads():
@@ -107,7 +125,7 @@ def _no_stale_loads():
def test_a_library_adapter_is_selected_for_its_kind(tmp_path: Path) -> None:
settings = _settings(tmp_path)
chosen = _write_adapter(settings.adapters_dir / "core")
source, state, reason = adapters.resolve_library_adapter(settings)
source, state, reason = adapters.resolve(adapters.library_sources(settings), settings, CORE)
assert source is not None and source.path == chosen
assert state == "available"
assert reason == "selected from the adapter library"
@@ -116,7 +134,7 @@ def test_a_library_adapter_is_selected_for_its_kind(tmp_path: Path) -> None:
def test_an_adapter_on_another_base_model_is_deferred_naming_both_models(tmp_path: Path) -> None:
settings = _settings(tmp_path)
_write_adapter(settings.adapters_dir / "router", kind=TrainingAdapterKind.hme_router, base_model="Qwen/Qwen3.5-0.8B")
source, state, reason = adapters.resolve_library_adapter(settings, TrainingAdapterKind.hme_router)
source, state, reason = adapters.resolve(adapters.library_sources(settings), settings, TrainingAdapterKind.hme_router)
assert source is None
assert state == "deferred"
assert "trained on Qwen/Qwen3.5-0.8B" in reason and f"runtime holds {MODEL}" in reason
@@ -125,7 +143,7 @@ def test_an_adapter_on_another_base_model_is_deferred_naming_both_models(tmp_pat
def test_a_manifest_without_weights_is_deferred_naming_the_missing_file(tmp_path: Path) -> None:
settings = _settings(tmp_path)
_write_adapter(settings.adapters_dir / "core", weights=False)
source, state, reason = adapters.resolve_library_adapter(settings)
source, state, reason = adapters.resolve(adapters.library_sources(settings), settings, CORE)
assert source is None and state == "deferred"
assert "adapter_model.safetensors" in reason
@@ -183,32 +201,70 @@ def test_stub_weights_prove_the_load_branch_runs(tmp_path: Path, monkeypatch) ->
assert row.state == "loaded" and "attached to the live model" in row.reason
def test_unload_clears_the_loaded_state(tmp_path: Path, monkeypatch) -> None:
def test_unload_stops_claiming_a_model_that_is_gone(tmp_path: Path, monkeypatch) -> None:
settings = _settings(tmp_path)
_write_adapter(settings.adapters_dir / "core")
monkeypatch.setattr(adapters, "load_into_model", lambda model, path: _FakeModel())
monkeypatch.setattr(QwenRuntime, "_load_base_model", lambda self: (_FakeModel(), object()))
runtime = QwenRuntime(settings)
runtime._load_transformers()
assert adapters.load_records(), "nothing was recorded as loaded"
database = Database(settings)
row = next(item for item in adapters.plan(settings, database).adapters if item.kind == CORE)
assert row.state == "loaded" and "live model" in row.reason
other = QwenRuntime(settings)
assert other._loaded_adapter() is None, "a second instance inherited another instance's claim"
runtime.unload()
assert adapters.load_records() == []
row = next(item for item in adapters.plan(settings, Database(settings)).adapters if item.kind == CORE)
row = next(item for item in adapters.plan(settings, database).adapters if item.kind == CORE)
assert row.state == "available"
assert "that model is gone" in row.reason, row.reason
def test_the_loser_of_the_choice_is_reported_as_shadowed(tmp_path: Path) -> None:
def test_a_rejected_candidate_does_not_claim_another_adapter_won(tmp_path: Path) -> None:
settings = _settings(tmp_path)
_write_adapter(settings.adapters_dir / "router", kind=TrainingAdapterKind.hme_router, base_model="Qwen/Qwen3.5-0.8B")
report = adapters.plan(settings, Database(settings))
router_rows = [row for row in report.adapters if row.kind == TrainingAdapterKind.hme_router]
assert len(router_rows) == 1, "a rejected candidate was published as a shadow row"
assert router_rows[0].state == "deferred" and "trained on Qwen/Qwen3.5-0.8B" in router_rows[0].reason
assert not any(row.shadowed for row in report.adapters), "nothing won, so nothing may claim a winner"
def test_the_shadow_row_names_the_adapter_that_won(tmp_path: Path) -> None:
settings = _settings(tmp_path)
_write_adapter(settings.adapters_dir / "first", adapter_id="written_first")
_write_adapter(settings.adapters_dir / "second", adapter_id="written_second")
report = adapters.plan(settings, Database(settings))
losers = [row for row in report.adapters if row.shadowed]
assert [row.adapter_id for row in losers] == ["written_first"]
assert "another adapter of this kind wins" in losers[0].reason
assert losers[0].reason == "shadowed by written_second for the current mount"
winner = next(row for row in report.adapters if row.adapter_id == "written_second")
assert winner.shadowed is False and winner.state == "available"
def test_a_cartridge_tagged_adapter_outranks_an_untagged_one(tmp_path: Path) -> None:
settings = _settings(tmp_path)
tagged = _write_adapter(settings.adapters_dir / "tagged", adapter_id="tagged")
manifest = json.loads((tagged / "adapter_manifest.json").read_text(encoding="utf-8"))
manifest["cartridge_id"] = "cart-not-mounted"
(tagged / "adapter_manifest.json").write_text(json.dumps(manifest), encoding="utf-8")
_write_adapter(settings.adapters_dir / "plain", adapter_id="plain")
source, _state, _reason = adapters.resolve(adapters.library_sources(settings), settings, CORE)
assert source is not None and source.manifest.adapter_id == "tagged"
def test_generation_reports_the_adapter_it_attached(tmp_path: Path, monkeypatch) -> None:
settings = _settings(tmp_path)
chosen = _write_adapter(settings.adapters_dir / "core")
monkeypatch.setitem(sys.modules, "qwen_vl_utils", SimpleNamespace(process_vision_info=lambda _messages: ([], None)))
monkeypatch.setattr(adapters, "load_into_model", lambda model, path: _FakeModel())
monkeypatch.setattr(QwenRuntime, "_load_base_model", lambda self: (_FakeModel(), _FakeProcessor()))
generation = QwenRuntime(settings).generate_prompt("what does the cartridge hold?")
assert generation.adapter_path == str(chosen)
assert generation.adapter_kind == CORE.value and generation.adapter_id == "adapter-1"
assert generation.adapter_sha256 == "a" * 64
assert generation.output_tokens == 3 and generation.text == "three tokens"
def test_an_unreadable_manifest_names_the_file(tmp_path: Path) -> None:
settings = _settings(tmp_path)
broken = settings.adapters_dir / "broken"