Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 30 additions & 15 deletions serve/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -8864,32 +8864,47 @@ def default_model_name(cfg: dict) -> str:
return cfg.get("model_name") or FAMILY_MODEL_NAMES.get(cfg.get("arch") or "qwen4exp", "qwen3.8-flash-next")


def _in_another_engine_package(path: str, package: Path) -> bool:
"""True when `path` sits in an engine package folder that is not `package` - the old folder an engine update leaves
behind. Only the components/engine/ layout counts, so a reader the person put somewhere else is left alone."""
parts = [part.lower() for part in Path(path).parent.parts]
if "components" not in parts or "engine" not in parts:
return False
try:
return Path(path).parent != package
except (OSError, ValueError):
return False


def with_current_reader(cfg: dict, package: Path) -> dict:
"""The picture reader (vision "exe", "cpu_exe") is written into the run config as an absolute path inside the engine package's folder,
and that folder is named after the engine version: after an engine update, or in a container made for another card, the path is gone
and the server would stop with "server_exited". Use the same file of the package that is installed now (the reader ships in the package)."""
"""The picture reader (vision "exe", "cpu_exe") is written into the run config as an absolute path inside the engine package's
folder, and that folder is named after the engine version: after an engine update, or in a container made for another card,
the path is gone and the server would stop with "server_exited". Use the same file of the package that is installed now (the
reader ships in the package).

A missing path is not the only stale case (#4658): `strata update` keeps the old engine folder, so the old reader is still
there and the server went on starting it after an update. A reader in another engine package folder is replaced as well; a
reader outside components/engine/ is the person's own file and is left as it is."""
vision = cfg.get("vision")
if not isinstance(vision, dict):
return cfg
fixed = dict(vision)
for key in ("exe", "cpu_exe"):
old = fixed.get(key)
if isinstance(old, str) and old and not engine_component.is_reference(old) and os.path.isabs(old):
new = package / old.replace("\\", "/").rsplit("/", 1)[-1]
gone = not os.path.exists(old)
# #4658: the old engine folder is kept after `strata update`, so the old reader still exists; one in a SIBLING engine folder
# (components/engine/<version>-...) is the engine's reader from before the update, and the engine in use has its own
older = (not gone and Path(old).parent != package and Path(old).parent.parent == package.parent
and package.parent.name.lower() == "engine")
if (gone or older) and new.is_file():
print(f"[strata] the picture reader {old} " + ("is not there any more" if gone else "belongs to an older engine")
+ f"; using {new} from the engine package in use", flush=True)
fixed[key] = str(new)
if not isinstance(old, str) or not old or engine_component.is_reference(old) or not os.path.isabs(old):
continue
gone = not os.path.exists(old)
if not gone and not _in_another_engine_package(old, package):
continue
new = package / old.replace("\\", "/").rsplit("/", 1)[-1]
if new.is_file():
why = "is not there any more" if gone else "belongs to an engine that is no longer the one in use"
print(f"[strata] the picture reader {old} {why}; using {new} from the engine package in use", flush=True)
fixed[key] = str(new)
if fixed == vision:
return cfg
return {**cfg, "vision": fixed}


def with_component_engine(cfg: dict) -> dict:
"""A run config that names "component:strata-engine" gets the engine package for this PC: the path in `exe`, and
the package's own folder first on the library path (the GPU runtime libraries ship beside the binary)."""
Expand Down
43 changes: 43 additions & 0 deletions serve/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from unittest import mock

sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from serve import server # noqa: E402 with_current_reader tests (#4658)
from serve.frontend import ChatTemplate, literal_tags, mark_think_literals, unmark_think_literals # noqa: E402
from serve.server import (ANSWER_RESERVE_MIN, CTX_SLACK, ByteTokenizer, EngineDied, EngineNonFinite, GpuBusy, MockEngine, # noqa: E402
PP_DONE_TAIL, Service,
Expand Down Expand Up @@ -6311,6 +6312,48 @@ def test_a_vision_answer_in_time_is_unchanged(self):
self.assertEqual(v._readline(5.0, "x"), "OK 7 1 1 1\n")


class CurrentReader(unittest.TestCase):
"""#4658: `strata update` keeps the old engine folder, so a missing reader was not the only stale case."""

def _package(self, root, version):
package = Path(root) / "components" / "engine" / version
package.mkdir(parents=True, exist_ok=True)
(package / "strata-vision.exe").write_bytes(b"reader")
return package

def test_a_reader_in_an_old_engine_package_is_replaced_even_when_it_still_exists(self):
with tempfile.TemporaryDirectory() as tmp:
old = self._package(tmp, "0.1.52-windows-x86_64-cuda-cuda13")
new = self._package(tmp, "0.1.54-windows-x86_64-cuda-cuda13")
cfg = {"vision": {"exe": str(old / "strata-vision.exe"), "mmproj": "m.gguf"}}
got = server.with_current_reader(cfg, new)
self.assertEqual(got["vision"]["exe"], str(new / "strata-vision.exe"))
self.assertEqual(got["vision"]["mmproj"], "m.gguf")

def test_a_missing_reader_is_still_replaced(self):
with tempfile.TemporaryDirectory() as tmp:
new = self._package(tmp, "0.1.54-linux-x86_64-cuda-cuda13")
gone = Path(tmp) / "components" / "engine" / "0.1.52-linux-x86_64-cuda-cuda13" / "strata-vision.exe"
cfg = {"vision": {"exe": str(gone)}}
got = server.with_current_reader(cfg, new)
self.assertEqual(got["vision"]["exe"], str(new / "strata-vision.exe"))

def test_a_reader_outside_the_engine_packages_is_left_alone(self):
with tempfile.TemporaryDirectory() as tmp:
new = self._package(tmp, "0.1.54-linux-x86_64-cuda-cuda13")
own = Path(tmp) / "my-encoders" / "strata-vision-q8"
own.parent.mkdir(parents=True)
own.write_bytes(b"mine")
cfg = {"vision": {"exe": str(own)}}
self.assertIs(server.with_current_reader(cfg, new), cfg)

def test_the_current_package_is_left_alone(self):
with tempfile.TemporaryDirectory() as tmp:
new = self._package(tmp, "0.1.54-linux-x86_64-cuda-cuda13")
cfg = {"vision": {"exe": str(new / "strata-vision.exe")}}
self.assertIs(server.with_current_reader(cfg, new), cfg)


if __name__ == "__main__":
unittest.main()

Expand Down
Loading