diff --git a/src/tether/exporters/monolithic.py b/src/tether/exporters/monolithic.py index 7725240..738c68c 100644 --- a/src/tether/exporters/monolithic.py +++ b/src/tether/exporters/monolithic.py @@ -144,6 +144,22 @@ def _require_monolithic_deps() -> None: except ImportError: missing.append(pip_name) + # lerobot version must be exactly 0.5.1 — the SmolVLA/pi0 export patches + # target its 0.5.1 module layout (SmolVLMWithExpertModel.embed_image, the + # masking_utils surface). A different lerobot imports fine but then fails + # the monkeypatch or exports a wrong graph — the common, confusing failure + # behind issue #190. Assert it explicitly rather than discovering it later. + try: + import lerobot + lerobot_ver = getattr(lerobot, "__version__", "unknown") + if lerobot_ver != "0.5.1": + missing.append( + f"lerobot==0.5.1 (found {lerobot_ver}; the monolithic export " + f"patches target lerobot 0.5.1's module layout exactly)" + ) + except ImportError: + pass # already reported as missing above + if missing: raise ImportError( "Missing dependencies for monolithic export:\n - " @@ -251,7 +267,17 @@ def _embed_image_with_explicit_patch_mask(self, image): _embed_image_with_explicit_patch_mask._tether_patched = True _smolvla.SmolVLMWithExpertModel.embed_image = _embed_image_with_explicit_patch_mask except Exception as exc: - logger.debug("SmolVLA explicit patch-mask export patch not installed: %s", exc) + # WARNING, not debug: if this patch fails to apply (e.g. a lerobot API + # change moved SmolVLMWithExpertModel.embed_image), the SmolVLA export + # proceeds with the UNPATCHED forward and can fail later with a cryptic + # mask-broadcast error — or export a subtly wrong graph. Surface it so + # the failure is attributable, not silent (relates to issue #190). + logger.warning( + "SmolVLA explicit patch-mask export patch did NOT apply (%s). " + "The export will continue unpatched; if it fails downstream with a " + "vision/attention-mask shape error, this is the likely cause. " + "Verify lerobot==0.5.1 is installed.", exc, + ) try: from transformers.models.smolvlm import modeling_smolvlm as _smolvlm diff --git a/tests/test_monolithic_version_guard.py b/tests/test_monolithic_version_guard.py new file mode 100644 index 0000000..6beefac --- /dev/null +++ b/tests/test_monolithic_version_guard.py @@ -0,0 +1,40 @@ +"""The monolithic export must reject a wrong lerobot version up front (#190). + +Previously only `transformers` was version-checked; `lerobot` was merely +imported, so a mismatched lerobot passed the dep check and failed later with a +confusing monkeypatch/mask error. +""" +from __future__ import annotations + +import sys +import types + +import pytest + +from tether.exporters import monolithic + + +def _fake_lerobot(version: str) -> types.ModuleType: + m = types.ModuleType("lerobot") + m.__version__ = version + return m + + +def test_wrong_lerobot_version_is_reported(monkeypatch): + monkeypatch.setitem(sys.modules, "lerobot", _fake_lerobot("0.4.0")) + with pytest.raises(ImportError) as ei: + monolithic._require_monolithic_deps() + assert "lerobot==0.5.1 (found 0.4.0" in str(ei.value) + + +def test_correct_lerobot_version_not_reported(monkeypatch): + monkeypatch.setitem(sys.modules, "lerobot", _fake_lerobot("0.5.1")) + # Other monolithic deps may be absent in this env (so it can still raise), + # but the lerobot-version complaint must NOT be among the reasons. + try: + monolithic._require_monolithic_deps() + msg = "" + except ImportError as e: + msg = str(e) + assert "found 0.5.1" not in msg + assert "lerobot==0.5.1 (found" not in msg