Preserve verified Redux model cards across revision upgrades

This commit is contained in:
baketnk committed 2026-10-04 21:16:22 -04:00
1 parent bbbd31fa6d
commit c0176b8595
6 files changed
+113 -6

No files matched your search

+1 -1
View File
@@ -16,7 +16,7 @@
{"path": "config.json", "size": 12988, "sha256": "503c653b2e3bb788adbcb04f5abdee532d958686564081baeed133ff10143f6e"},
{"path": "ternary.json", "size": 57970, "sha256": "1221c6d3ce901ffe09c089da758a8db8b76189f80cff41c5afc244fc61e2051d"},
{"path": "tokenizer.json", "size": 1159960, "sha256": "bd321b096832a3f270bd3b2a88823957920f1a5c5ada71114a26ea729d0cbe91"},
{"path": "README.md", "size": 8832, "sha256": "930055e6bac7d75114e719850ffaffc8d327e73b4295518c38c81399acd63e46"}
{"path": "README.md", "size": 8832, "sha256": "930055e6bac7d75114e719850ffaffc8d327e73b4295518c38c81399acd63e46", "compatible": [{"size": 8533, "sha256": "a8b327f983a8b8ff262ff7bead3a791fbed9350632002af8db85ab5cd84cdaa5"}]}
]
},
"attribution": "Moondream/M87 Labs, Parakeet Redux, derived from NVIDIA Parakeet TDT 0.6B v3; original model card README.md retained with the files.",
+5
View File
@@ -232,6 +232,11 @@ back to desktop/cloud inference. The benchmark ran on revision
`fad622f25f303105c20d70e201bcc477c88b620c`, which no longer resolves after the
upstream history was rewritten; the weights, config, ternary map and tokenizer at
`2bf1286` are byte-identical to it (same SHA-256), only the model card README differs.
The manifest explicitly accepts the previous card's pinned size and SHA-256 for
existing installations, so updating FrameYap requires no model download. New
downloads must verify against the current revision's card; unknown or modified
cards are still rejected. The optional per-file `compatible` list contains exact
`size`/`sha256` pairs for previously installed artifacts, never download fallbacks.
The earlier benchmark used this local API (its source belongs to the originating
repository, not this project):
+26 -5
View File
@@ -61,6 +61,7 @@ class ModelFile:
path: str
size: int
sha256: str
compatible: tuple[tuple[int, str], ...] = ()
@dataclass(frozen=True)
@@ -127,13 +128,28 @@ def _parse(value):
parsed = []
names = set()
for item in files:
_fields(item, ("path", "size", "sha256"), "model file")
if not isinstance(item, dict):
raise ManifestError("invalid model file fields")
_fields(item, ("path", "size", "sha256", "compatible") if "compatible" in item
else ("path", "size", "sha256"), "model file")
name = _relative(item["path"], "model file path")
size = item["size"]
if name in names or type(size) is not int or not 0 < size <= _MAX_FILE or not isinstance(item["sha256"], str) or not _HASH.fullmatch(item["sha256"]):
raise ManifestError("duplicate or invalid pinned model file")
names.add(name)
parsed.append(ModelFile(name, size, item["sha256"]))
compatible = item.get("compatible", [])
if not isinstance(compatible, list) or len(compatible) > 8:
raise ManifestError("invalid compatible model files")
pins = []
for previous in compatible:
_fields(previous, ("size", "sha256"), "compatible model file")
old_size, old_hash = previous["size"], previous["sha256"]
if (type(old_size) is not int or not 0 < old_size <= _MAX_FILE or
not isinstance(old_hash, str) or not _HASH.fullmatch(old_hash) or
(old_size, old_hash) in [(size, item["sha256"]), *pins]):
raise ManifestError("duplicate or invalid compatible model file")
pins.append((old_size, old_hash))
parsed.append(ModelFile(name, size, item["sha256"], tuple(pins)))
license_info = value["license"]
_fields(license_info, ("id", "text"), "license")
requirements = value["requirements"]
@@ -177,7 +193,11 @@ def load_backends(manifest_dir=DEFAULT_MANIFEST_DIR):
def check_file(directory, item):
"""Return (reason, filename); reason None means the pinned file verifies."""
"""Verify the current pin or an explicitly compatible existing artifact.
Downloaders construct a temporary ModelFile with only the current pin so
newly fetched bytes must match the revision advertised to the user.
"""
root = Path(directory)
if not root.is_absolute():
return "model_dir_not_absolute", item.path
@@ -201,13 +221,14 @@ def check_file(directory, item):
before = os.fstat(file_fd)
if not stat.S_ISREG(before.st_mode):
return "unsafe_file", item.path
if before.st_size != item.size:
pins = ((item.size, item.sha256), *item.compatible)
if before.st_size not in {size for size, _ in pins}:
return "size_mismatch", item.path
digest = hashlib.sha256()
while chunk := os.read(file_fd, 1024 * 1024):
digest.update(chunk)
after = os.fstat(file_fd)
if (before.st_size, before.st_mtime_ns, before.st_ctime_ns) != (after.st_size, after.st_mtime_ns, after.st_ctime_ns) or digest.hexdigest() != item.sha256:
if (before.st_size, before.st_mtime_ns, before.st_ctime_ns) != (after.st_size, after.st_mtime_ns, after.st_ctime_ns) or (before.st_size, digest.hexdigest()) not in pins:
return "hash_mismatch", item.path
return None, None
finally:
+7
View File
@@ -21,6 +21,9 @@ class BackendTests(unittest.TestCase):
"tokenizer.json": (1159960, "bd321b096832a3f270bd3b2a88823957920f1a5c5ada71114a26ea729d0cbe91"),
"README.md": (8832, "930055e6bac7d75114e719850ffaffc8d327e73b4295518c38c81399acd63e46"),
})
self.assertEqual(redux.files[-1].compatible,
((8533, "a8b327f983a8b8ff262ff7bead3a791fbed9350632002af8db85ab5cd84cdaa5"),))
self.assertTrue(all(not file.compatible for file in redux.files[:-1]))
self.assertEqual(redux.launcher["protocol"], "frameyap-worker-v1")
self.assertEqual(redux.launcher["arguments"], ["--model", "{model_dir}", "--threads", "{threads}", "--clip-dir", "{clip_dir}"])
self.assertIn("CC-BY-4.0", redux.license_id)
@@ -47,6 +50,10 @@ class BackendTests(unittest.TestCase):
lambda f: f["model"]["files"][0].update(path="../secret"),
lambda f: f["model"]["files"][0].update(size=True),
lambda f: f["model"]["files"][0].update(sha256="A" * 64),
lambda f: f["model"]["files"][0].update(compatible="bad"),
lambda f: f["model"]["files"][0].update(compatible=[{"size": True, "sha256": "a" * 64}]),
lambda f: f["model"]["files"][0].update(compatible=[{"size": 3, "sha256": "a" * 64}]),
lambda f: f["model"]["files"][0].update(compatible=[{"size": 4, "sha256": "a" * 64, "path": "other"}]),
lambda f: f["launcher"]["arguments"].append("{unknown}"),
lambda f: f["launcher"].update(path="/bin/sh"),
lambda f: f["launcher"].update(protocol="not-the-wire-protocol"),
+51
View File
@@ -1206,6 +1206,57 @@ with patch.object(module, "check_host"), patch.object(module.urllib.request, "ur
self.assertIn("mismatched", json.loads(output.getvalue())["message"])
fetch.assert_not_called()
def test_model_card_upgrade_retains_verified_legacy_without_network(self):
shutil.copyfile(REPO / "python/frameyap/model_files.py",
self.stage / "python/frameyap/model_files.py")
manifest = json.loads((REPO / "assets/backends/redux.json").read_text())
old, new = b"previous attributed card", b"updated attributed model card"
def pin(content):
return {"size": len(content), "sha256": hashlib.sha256(content).hexdigest()}
manifest["model"]["files"] = [{"path": "README.md", **pin(old)}]
manifests = self.stage / "assets/backends"
manifests.mkdir()
card_manifest = manifests / "redux.json"
card_manifest.write_text(json.dumps(manifest))
archive, digest = self.package("0.1.202609241530")
self.install("0.1.202609241530", archive, digest, "--without-model")
destination = self.base / "models"
destination.mkdir()
card = destination / "README.md"
card.write_bytes(old)
manifest["model"]["files"] = [{"path": "README.md", **pin(new),
"compatible": [pin(old)]}]
card_manifest.write_text(json.dumps(manifest))
archive, digest = self.package("0.1.202609241531")
with patch.object(installer.urllib.request, "urlopen") as fetch:
self.install("0.1.202609241531", archive, digest, "--without-model")
module, backend, _ = installer.installed_backend(self.data / "frameyap", "redux")
self.assertEqual(module.check_model(backend, destination)["state"], "installed_verified")
argv = ["--install-model", "--model-dir", str(destination), "--yes", "--json"]
with contextlib.redirect_stdout(io.StringIO()):
self.assertEqual(installer.cli(argv), 0)
fetch.assert_not_called()
self.assertEqual(card.read_bytes(), old)
card.write_bytes(b"x" * len(old))
with contextlib.redirect_stdout(io.StringIO()):
self.assertEqual(installer.cli(argv), 1)
fetch.assert_not_called()
card.unlink()
class Response(io.BytesIO):
def geturl(self):
return "https://huggingface.co/fixture"
# Fresh downloads require the current card even though the old one is
# permitted for already installed, independently hashed model files.
with patch.object(installer.urllib.request, "urlopen", return_value=Response(old)):
with contextlib.redirect_stdout(io.StringIO()):
self.assertEqual(installer.cli(argv), 1)
self.assertFalse(card.exists())
with patch.object(installer.urllib.request, "urlopen", return_value=Response(new)) as fetch:
with contextlib.redirect_stdout(io.StringIO()):
self.assertEqual(installer.cli(argv), 0)
fetch.assert_called_once()
self.assertEqual(card.read_bytes(), new)
def test_runtime_install_is_explicit_cpu_pinned_and_updates_only_python_path(self):
# subprocess is mocked: no venv, pip or network is touched.
root = self.data / "frameyap"
+23
View File
@@ -74,6 +74,29 @@ class ModelFileTests(unittest.TestCase):
(model / "nested").symlink_to(root)
self.assertEqual(check_model(backend, model)["reason"], "unsafe_file")
def test_compatible_card_still_requires_exact_hash_and_safe_file(self):
with tempfile.TemporaryDirectory() as path:
root = Path(path)
_, backend = self.fixture(root)
current, previous = b"current card", b"old card"
item = ModelFile("README.md", len(current), hashlib.sha256(current).hexdigest(),
((len(previous), hashlib.sha256(previous).hexdigest()),))
backend = replace(backend, files=(item,))
card = root / "README.md"
for content in (current, previous):
card.write_bytes(content)
self.assertEqual(check_model(backend, root)["state"], "installed_verified")
# A download temp pin must never accept an older artifact.
self.assertEqual(check_file(root, ModelFile(item.path, item.size, item.sha256))[0],
"size_mismatch")
card.write_bytes(b"bad card")
self.assertEqual(check_model(backend, root)["reason"], "hash_mismatch")
card.unlink()
outside = root / "outside"
outside.write_bytes(previous)
card.symlink_to(outside)
self.assertEqual(check_model(backend, root)["reason"], "unsafe_file")
def test_second_backend_status_without_runtime_changes(self):
with tempfile.TemporaryDirectory() as path:
root = Path(path)