Prepare inspected static Metal runtime inventory for macOS staging

This commit is contained in:
karl committed 2026-10-13 13:26:16 +02:00
1 parent 07bbcb76e8
commit 4a7c4acdcb
11 files changed
+236 -13

No files matched your search

@@ -45,6 +45,7 @@ if(SEARCHI_ACCELERATION STREQUAL "cpu")
set(GGML_METAL OFF CACHE BOOL "CPU test build" FORCE)
elseif(SEARCHI_ACCELERATION STREQUAL "metal" OR (SEARCHI_ACCELERATION STREQUAL "auto" AND APPLE))
set(GGML_METAL ON CACHE BOOL "Production Metal backend" FORCE)
set(GGML_METAL_EMBED_LIBRARY ON CACHE BOOL "Standalone worker contains Metal kernels" FORCE)
elseif(SEARCHI_ACCELERATION STREQUAL "cuda")
set(GGML_CUDA ON CACHE BOOL "Production CUDA backend" FORCE)
elseif(SEARCHI_ACCELERATION STREQUAL "vulkan")
+5 -1
View File
@@ -8,7 +8,11 @@ Windows needs Visual Studio C++ build tools, a Windows SDK, and WebView2.
Linux desktop builds need WebKitGTK 4.1 development libraries; macOS needs
Xcode command-line tools. Follow the [Tauri prerequisites](https://v2.tauri.app/start/prerequisites/).
Headless Linux CLI/worker builds do not need WebKitGTK or a display server.
They still need a C/C++ compiler, linker, CMake, and Rust: SQLite, USearch,
macOS runtime preparation embeds Metal kernel sources and inspects the static
worker's Apple dependencies before staging. See [macOS runtime](MACOS-RUNTIME.md)
for the prepared workflow and explicitly unexecuted native checks.
Headless builds still need a C/C++ compiler, linker, CMake, and Rust: SQLite, USearch,
NumKong, and the Google worker contain native code.
## Windows CPU development
+49
View File
@@ -0,0 +1,49 @@
# macOS runtime preparation
Normal macOS `auto` builds select Metal with CPU fallback. The worker links
llama/ggml statically and uses Apple system libraries/frameworks. Searchi now
forces `GGML_METAL_EMBED_LIBRARY=ON` so upstream Metal kernel sources reside
inside the worker rather than requiring loose kernel files from the build tree.
After compilation, the builder inspects the artifact using Apple `lipo` and
`otool`; it does not execute the worker or initialize a GPU. Inspection requires:
- Supported arm64/x86_64 architectures, including both for a universal artifact.
- Only explicitly allowed Apple system libraries/frameworks, including Metal.
- A nonempty `__DATA,__ggml_metallib` section for each architecture.
- Current build-input provenance and a SHA-256 of the resulting worker.
The generated inventory declares the `static-metal-embedded` layout with an
empty packaged-library list. Normal macOS staging now requires this inventory;
the previous optional-inventory exception is removed for `auto`/`metal`.
Staging verifies source identity, worker bytes, Mach-O magic and the static
layout instead of requiring Windows DLLs or Linux shared libraries. Explicit
internal CPU tooling keeps its optional-inventory behavior.
On a native Mac with the required Apple build tools:
```sh
python3 tools/embeddinggemma2/build_runtime.py --backend auto --jobs 2
python3 tools/embeddinggemma2/stage_runtime.py --backend auto
```
These commands are prepared, **not executed on macOS**. Native compilation,
actual `lipo`/`otool` output, relocation outside the source tree, genuine Google
CPU/Metal inference, device fallback, app integration and signing/notarization
remain open. This change does not create a macOS release download.
## Evidence, 2026-10-13
Four controlled Mach-O metadata-fixture tests passed, covering static system
dependencies, foreign library rejection, missing Metal data and per-architecture
section coverage. Staging tests exercise the empty-library macOS layout and the
mandatory normal-profile policy. These fixtures are not executable Mac builds.
Across macOS metadata, staging, builder, provenance and Windows/Linux packaging
suites: 75 tests passed with one platform-specific skip, in normal and optimized
Python modes. Actual Windows and Ubuntu WSL normal runtime builds passed after
the recipe change. Windows staging and Linux inventory verification passed.
Worker hashes remain identical to their previously tested CPU deployments:
Windows `c01cb3ec13cfca722ee768e8350e1399a8b9a4d14a24a35adc3473a5cfc82a73`;
Linux `b52d50a1e676bd4bf64124daff1c4ce83d86a8bde01e4eaf09838cbb24a4c28f`.
No GPU inference or native macOS execution was performed.
+4
View File
@@ -122,6 +122,10 @@ release. Keep their evidence and unchecked production gates intact.
- [x] Normal Windows/Linux source-build provenance gate before staging and
archive preparation; stale inventories preserve existing resources/packages.
Actual normal builds verified. See [runtime provenance](RUNTIME-PROVENANCE.md).
- [x] Prepare static macOS Metal inventory and mandatory normal-profile staging,
with embedded-source/dependency/architecture metadata regressions.
- [ ] Execute native macOS runtime build, relocation and genuine CPU/Metal
inference; fixture metadata tests do not satisfy this gate.
- [ ] Verify power-loss durability and release-tool interaction with resource
directory swaps on each OS; fault-injected process interruption is narrower.
- [x] Shared monotonic Basic/Debug search metrics, granular bounded engine traces,
+9 -8
View File
@@ -7,8 +7,8 @@ release preparation now records and checks build-input identity before staging
or packaging a normal Windows/Linux runtime.
`runtime-libraries.json` includes `build_provenance`, schema 1, the pinned
llama.cpp commit and SHA-256 of six bounded inputs: worker.cpp, native
CMakeLists.txt, build_runtime.py and the three runtime patches. Only CRLF line
llama.cpp commit and SHA-256 of seven bounded inputs: worker.cpp, native
CMakeLists.txt, build_runtime.py, the macOS artifact inspector and the three runtime patches. Only CRLF line
endings in these build inputs are canonicalized to LF, allowing the same
source revision to compare across checkouts. Indexed user-file bytes and their
extraction provenance are unaffected.
@@ -29,8 +29,8 @@ check and were not altered retroactively.
## Recorded evidence, 2026-10-13
- 69 tests passed across provenance, builder, staging, Windows packaging and
Linux packaging suites; one platform-specific staging test remained skipped.
- 75 tests passed across provenance, builder, macOS metadata, staging, Windows
packaging and Linux packaging suites; one platform-specific staging test remained skipped.
The same suites passed under optimized Python. Regressions reject a valid old
inventory without provenance, changed source and oversized inputs, while
preserving existing archives. Build-source CRLF/LF equivalence is checked.
@@ -39,17 +39,18 @@ check and were not altered retroactively.
`c01cb3ec13cfca722ee768e8350e1399a8b9a4d14a24a35adc3473a5cfc82a73`;
all 25 Windows DLL hashes also match the previously tested release exactly.
New staged inventory SHA-256:
`7fde510984b4635ce127d25ad91aab9a391300d7968ac71e4f201cc8edd052e8`.
`b3859169d2c21ee5d40cdca038c3494f5620fe544adb2dd404d4535c89b5076b`.
- An actual historical extracted Windows inventory was rejected by normal
staging preflight for missing provenance. Its files were only read.
- Actual normal Linux `auto` build passed on Ubuntu 24.04 WSL. The packager
verified 21 runtime inventory/files and all six source identities against the
verified 21 runtime inventory/files and all seven source identities against the
current repository, including checkout line-ending differences.
Worker SHA-256 remains
`b52d50a1e676bd4bf64124daff1c4ce83d86a8bde01e4eaf09838cbb24a4c28f`.
New inventory SHA-256:
`de6e06c68815b35d83c6acbde468d100413f47c74536f0d4219a9edcbeb01a86`.
`6463ae8a126f8de3ca4349bbb02fd36852f30222fe30e11fb4070385ce1989e8`.
These normal builds retain GPU modules plus CPU fallback; no GPU inference was
executed. macOS inventory integration, cross-compilation and actual GPU checks
executed. macOS inventory preparation is now implemented and fixture-tested;
[native macOS checks](MACOS-RUNTIME.md), cross-compilation and actual GPU checks
remain open. Published Windows and Linux assets retain their original hashes.
+10
View File
@@ -10,6 +10,7 @@ import shutil
import subprocess
import sys
from runtime_provenance import fingerprint, UPSTREAM_COMMIT
from macos_runtime import inspect as inspect_macos
ROOT = Path(__file__).resolve().parents[2]
TAG = "b11461"
@@ -215,6 +216,15 @@ def main():
os.replace(temporary, directory / "runtime-libraries.json")
if linux_dispatch(args.backend):
bundle_linux_libraries(build, provenance)
if sys.platform == "darwin" and args.backend in {"auto", "metal"}:
worker = build / "searchi-gemma-worker"
inventory = inspect_macos(worker)
with worker.open("rb") as stream:
inventory["worker_sha256"] = hashlib.file_digest(stream, "sha256").hexdigest()
inventory["build_provenance"] = provenance
temporary = build / "runtime-libraries.json.partial"
temporary.write_text(json.dumps(inventory, indent=2), encoding="utf-8")
os.replace(temporary, build / "runtime-libraries.json")
print(f"Runtime build complete: {build}")
+69
View File
@@ -0,0 +1,69 @@
"""Inspect a static Metal worker using Apple's tools, without executing it."""
import re
import subprocess
from pathlib import Path
MAGIC = {b"\xcf\xfa\xed\xfe", b"\xfe\xed\xfa\xcf", b"\xca\xfe\xba\xbe",
b"\xbe\xba\xfe\xca", b"\xca\xfe\xba\xbf", b"\xbf\xba\xfe\xca"}
SYSTEM_LIBRARIES = {"/usr/lib/libSystem.B.dylib", "/usr/lib/libc++.1.dylib",
"/usr/lib/libc++abi.dylib", "/usr/lib/libobjc.A.dylib"}
FRAMEWORKS = {"Foundation", "CoreFoundation", "Metal", "MetalKit", "Accelerate"}
def system_dependency(name):
if name in SYSTEM_LIBRARIES:
return True
return any(re.fullmatch(rf"/System/Library/Frameworks/{framework}\.framework/Versions/[A-Z]/{framework}", name)
for framework in FRAMEWORKS)
def validate_inventory(manifest, worker):
with Path(worker).open("rb") as stream:
if stream.read(4) not in MAGIC:
raise RuntimeError("macOS worker is not a Mach-O artifact")
arches = manifest.get("architectures")
dependencies = manifest.get("system_libraries")
if (manifest.get("platform") != "macos" or manifest.get("layout") != "static-metal-embedded" or manifest.get("libraries") != []
or manifest.get("metal_embedded") is not True
or not isinstance(arches, list) or not 1 <= len(arches) <= 2
or not all(isinstance(arch, str) for arch in arches)
or len(set(arches)) != len(arches) or not set(arches) <= {"arm64", "x86_64"}
or not isinstance(dependencies, list) or not 1 <= len(dependencies) <= 32
or not all(isinstance(name, str) and system_dependency(name) for name in dependencies)
or not any("/Metal.framework/" in name for name in dependencies)):
raise RuntimeError("Invalid static Metal runtime inventory")
def inspect(worker):
worker = Path(worker)
def tool(arguments):
try:
output = subprocess.check_output(arguments, text=True, timeout=30)
except FileNotFoundError as error:
raise RuntimeError("macOS runtime inspection requires Apple lipo/otool") from error
if len(output.encode("utf-8")) > 65536:
raise RuntimeError("macOS metadata exceeds inspection limit")
return output
arches = tool(["lipo", "-archs", str(worker)]).split()
dependencies = []
for line in tool(["otool", "-L", str(worker)]).splitlines():
if not line.strip():
continue
if line == f"{worker}:" or line in {f"{worker} (architecture {arch}):" for arch in arches}:
continue
match = re.fullmatch(r"\s+(.+?) \(compatibility version [0-9.]+, current version [0-9.]+\)", line)
if not match:
raise RuntimeError("Unrecognized Mach-O dependency metadata")
if match[1] not in dependencies:
dependencies.append(match[1])
sections = tool(["otool", "-l", str(worker)])
sizes = re.findall(r"sectname\s+__ggml_metallib\s+segname\s+__DATA\s+addr\s+0x[0-9a-fA-F]+\s+size\s+(0x[0-9a-fA-F]+)", sections)
if len(sizes) != len(arches) or not all(int(size, 16) > 0 for size in sizes):
raise RuntimeError("Each Mach-O architecture must contain nonempty embedded Metal kernels")
result = {"platform": "macos", "layout": "static-metal-embedded",
"architectures": arches, "system_libraries": sorted(dependencies),
"metal_embedded": True, "libraries": []}
validate_inventory(result, worker)
return result
@@ -8,6 +8,7 @@ INPUTS = (
"crates/searchi-semantic/native/worker.cpp",
"crates/searchi-semantic/native/CMakeLists.txt",
"tools/embeddinggemma2/build_runtime.py",
"tools/embeddinggemma2/macos_runtime.py",
"tools/embeddinggemma2/no-logits.patch",
"tools/embeddinggemma2/cpu-dispatch.patch",
"tools/embeddinggemma2/cpu-only-loader.patch",
+15 -4
View File
@@ -11,11 +11,17 @@ import stat
import time
import uuid
from runtime_provenance import verify as verify_provenance
from macos_runtime import validate_inventory as validate_macos
ROOT = Path(__file__).resolve().parents[2]
CRT_LIBRARIES = {"concrt140.dll", "msvcp140.dll", "msvcp140_1.dll", "msvcp140_2.dll", "msvcp140_atomic_wait.dll", "msvcp140_codecvt_ids.dll", "vccorlib140.dll", "vcruntime140.dll", "vcruntime140_1.dll", "vcruntime140_threads.dll"}
def inventory_required(backend, windows, system):
return ((windows or system.startswith("linux")) and backend in {"auto", "vulkan"}
or system == "darwin" and backend in {"auto", "metal"})
def library_name(name):
return isinstance(name, str) and bool(re.fullmatch(
r"(?:[A-Za-z0-9_-]+\.dll|lib[A-Za-z0-9_-]+\.so(?:\.\d+)*)", name))
@@ -349,7 +355,8 @@ def verify_runtime(source, required=False):
if hashlib.file_digest(stream, "sha256").hexdigest() != manifest.get("worker_sha256"):
raise RuntimeError("Runtime worker checksum mismatch")
records = manifest.get("libraries")
if not isinstance(records, list) or not 1 <= len(records) <= 64:
macos = manifest.get("platform") == "macos"
if not isinstance(records, list) or not (0 if macos else 1) <= len(records) <= 64:
raise RuntimeError("Invalid runtime dependency inventory")
paths, names = [], set()
for record in records:
@@ -373,9 +380,13 @@ def verify_runtime(source, required=False):
raise RuntimeError(f"Runtime library checksum mismatch: {name}")
paths.append(artifact)
if required:
if (source.suffix == ".exe") == (manifest.get("platform") == "linux"):
platform = manifest.get("platform")
if (source.suffix == ".exe" and platform not in {None, "windows"}) or (source.suffix != ".exe" and platform not in {"linux", "macos"}):
raise RuntimeError("Runtime inventory platform does not match worker")
if manifest.get("platform") == "linux":
if macos:
validate_macos(manifest, source)
complete = not names and source.suffix != ".exe"
elif manifest.get("platform") == "linux":
core = {"ggml", "ggml-base", "llama", "mtmd"}
present = {name for name in core if any(re.fullmatch(rf"lib{re.escape(name)}\.so(?:\.\d+)*", file) for file in names)}
complete = present == core and {"libggml-cpu-x64.so", "libggml-vulkan.so"}.issubset(names)
@@ -421,7 +432,7 @@ def main():
license_path = ROOT / ".searchi/references/llama.cpp/LICENSE"
if not license_path.is_file():
raise RuntimeError("Native worker license is missing")
required_inventory = (os.name == "nt" or os.sys.platform.startswith("linux")) and args.backend in {"auto", "vulkan"}
required_inventory = inventory_required(args.backend, os.name == "nt", os.sys.platform)
dependencies = verify_runtime(source, required_inventory)
crt_notice = source.parent / "MSVC-REDISTRIBUTION.txt"
if os.name == "nt" and args.backend != "cpu":
@@ -0,0 +1,50 @@
from pathlib import Path
import tempfile
import unittest
from unittest.mock import patch
import macos_runtime
class MacRuntimeTests(unittest.TestCase):
def inspect_fixture(self, dependency=None, sections=1, arches="arm64"):
with tempfile.TemporaryDirectory(prefix="mac metadata ") as directory:
worker = Path(directory) / "searchi-gemma-worker"
worker.write_bytes(b"\xcf\xfa\xed\xfe" + b"metadata fixture, not executable")
libraries = f"{worker}:\n\t/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 100.0.0)\n"
libraries += "\t" + (dependency or "/System/Library/Frameworks/Metal.framework/Versions/A/Metal") + " (compatibility version 1.0.0, current version 100.0.0)\n"
loads = "sectname __ggml_metallib\nsegname __DATA\naddr 0x1000\nsize 0x100\n" * sections
with patch.object(macos_runtime.subprocess, "check_output", side_effect=[arches, libraries, loads]):
return macos_runtime.inspect(worker)
def test_static_system_metal_inventory_has_no_packaged_dylibs(self):
result = self.inspect_fixture()
self.assertEqual(result["architectures"], ["arm64"])
self.assertEqual(result["libraries"], [])
self.assertTrue(result["metal_embedded"])
def test_external_library_and_missing_metal_are_rejected(self):
for dependency in ("/private/build path/libggml.dylib", "/usr/lib/libc++.1.dylib"):
with self.subTest(dependency=dependency), self.assertRaises(RuntimeError):
self.inspect_fixture(dependency=dependency)
def test_all_architectures_require_embedded_sources(self):
with self.assertRaisesRegex(RuntimeError, "Each Mach-O architecture"):
self.inspect_fixture(sections=0)
with self.assertRaisesRegex(RuntimeError, "Each Mach-O architecture"):
self.inspect_fixture(sections=1, arches="arm64 x86_64")
self.assertEqual(self.inspect_fixture(sections=2, arches="arm64 x86_64")["architectures"], ["arm64", "x86_64"])
def test_malformed_inventory_and_foreign_artifact_fail(self):
with tempfile.TemporaryDirectory() as directory:
worker = Path(directory) / "worker"
worker.write_bytes(b"\xcf\xfa\xed\xfe")
with self.assertRaises(RuntimeError):
macos_runtime.validate_inventory({"architectures": [["arm64"]]}, worker)
worker.write_bytes(b"MZxx")
with self.assertRaisesRegex(RuntimeError, "Mach-O"):
macos_runtime.validate_inventory({}, worker)
if __name__ == "__main__":
unittest.main()
@@ -13,6 +13,29 @@ import runtime_provenance
class StagingTests(unittest.TestCase):
def test_normal_macos_requires_inventory_and_internal_cpu_remains_optional(self):
self.assertTrue(stage_runtime.inventory_required("auto", False, "darwin"))
self.assertTrue(stage_runtime.inventory_required("metal", False, "darwin"))
self.assertFalse(stage_runtime.inventory_required("cpu", False, "darwin"))
self.assertTrue(stage_runtime.inventory_required("auto", True, "win32"))
self.assertTrue(stage_runtime.inventory_required("auto", False, "linux"))
def test_static_macos_inventory_accepts_no_dynamic_libraries(self):
with tempfile.TemporaryDirectory() as directory:
worker = Path(directory) / "searchi-gemma-worker"
worker.write_bytes(b"\xcf\xfa\xed\xfe" + b"metadata fixture, not executable")
manifest = {"platform": "macos", "layout": "static-metal-embedded",
"architectures": ["arm64"], "metal_embedded": True, "libraries": [],
"system_libraries": ["/usr/lib/libSystem.B.dylib", "/System/Library/Frameworks/Metal.framework/Versions/A/Metal"],
"worker_sha256": hashlib.sha256(worker.read_bytes()).hexdigest(),
"build_provenance": runtime_provenance.fingerprint()}
inventory = worker.parent / "runtime-libraries.json"
inventory.write_text(json.dumps(manifest))
self.assertEqual(stage_runtime.verify_runtime(worker, True), [inventory])
manifest["metal_embedded"] = False
inventory.write_text(json.dumps(manifest))
with self.assertRaises(RuntimeError):
stage_runtime.verify_runtime(worker, True)
def test_linux_inventory_stages_core_and_modules_and_removes_previous_owned_library(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)