Package portable Linux runtime and verify Google CPU search
This commit is contained in:
1 parent
e3558a0104
commit
0b56ab553c
22 files changed
+1585
-16
No files matched your search
@@ -3,6 +3,7 @@ import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
@@ -17,6 +18,87 @@ WINDOWS_CRT_FILES = (
|
||||
"msvcp140_atomic_wait.dll", "msvcp140_codecvt_ids.dll", "vccorlib140.dll",
|
||||
"vcruntime140.dll", "vcruntime140_1.dll", "vcruntime140_threads.dll",
|
||||
)
|
||||
LINUX_CPU_VARIANTS = {"x64", "sse42", "sandybridge", "ivybridge", "piledriver", "haswell",
|
||||
"skylakex", "cannonlake", "cascadelake", "icelake", "cooperlake", "zen4",
|
||||
"alderlake", "sapphirerapids"}
|
||||
LINUX_SYSTEM_LIBRARIES = {"libc.so.6", "libm.so.6", "libdl.so.2", "libpthread.so.0",
|
||||
"librt.so.1", "libstdc++.so.6", "libgcc_s.so.1", "ld-linux-x86-64.so.2"}
|
||||
|
||||
|
||||
def linux_dispatch(backend):
|
||||
"""Only normal Linux Vulkan modes use the packaged shared-module layout."""
|
||||
return (sys.platform.startswith("linux") and platform.machine().lower() in {"amd64", "x86_64"}
|
||||
and backend in {"auto", "vulkan"})
|
||||
|
||||
|
||||
def elf_dynamic(path):
|
||||
"""Read ELF metadata without executing the artifact or following ldd hooks."""
|
||||
output = subprocess.check_output(["readelf", "--dynamic", str(path)], text=True)
|
||||
values = {}
|
||||
for key, value in re.findall(r"\((NEEDED|SONAME|RUNPATH|RPATH)\).*?\[([^\]]+)\]", output):
|
||||
values.setdefault(key, []).append(value)
|
||||
return values
|
||||
|
||||
|
||||
def bundle_linux_libraries(build):
|
||||
"""Flatten approved SONAME files beside the worker and record dependency closure.
|
||||
|
||||
System libc/C++ ABI remains a declared distro compatibility gate. Vulkan's
|
||||
loader is optional: only the dynamically loaded GPU module may depend on it.
|
||||
Preflight precedes flattening; the preceding CMake build itself is not a
|
||||
transactional release replacement. Staging performs that separate swap.
|
||||
"""
|
||||
build = Path(build)
|
||||
worker = build / "searchi-gemma-worker"
|
||||
candidates = [build / "bin" / f"lib{name}.so" for name in ("ggml", "ggml-base", "llama", "mtmd")]
|
||||
candidates += [build / f"libggml-cpu-{variant}.so" for variant in sorted(LINUX_CPU_VARIANTS)]
|
||||
candidates.append(build / "libggml-vulkan.so")
|
||||
records = []
|
||||
names = set()
|
||||
for link in candidates:
|
||||
if not link.is_file():
|
||||
raise RuntimeError(f"Required Linux runtime library is missing: {link.name}")
|
||||
source = link.resolve()
|
||||
if not source.is_relative_to(build.resolve()) or not source.is_file():
|
||||
raise RuntimeError("Linux runtime library escapes its build directory")
|
||||
metadata = elf_dynamic(source)
|
||||
sonames = metadata.get("SONAME", [])
|
||||
module = link.parent == build
|
||||
if ((not module and len(sonames) != 1) or len(sonames) > 1
|
||||
or any(not re.fullmatch(r"lib[A-Za-z0-9_-]+\.so(?:\.\d+)*", name) for name in sonames)):
|
||||
raise RuntimeError("Linux runtime has invalid or missing SONAME")
|
||||
# Backend discovery uses the unversioned module filename; core linkage
|
||||
# uses its SONAME. Both destinations are actual files, never symlinks.
|
||||
name = link.name if module else sonames[0]
|
||||
if name in names:
|
||||
raise RuntimeError("Duplicate Linux runtime library")
|
||||
names.add(name)
|
||||
destination = build / name
|
||||
records.append((source, destination, metadata))
|
||||
all_names = names
|
||||
for path, metadata in [(worker, elf_dynamic(worker)), *[(destination, metadata) for _, destination, metadata in records]]:
|
||||
if metadata.get("RPATH") or metadata.get("RUNPATH") != ["$ORIGIN"]:
|
||||
raise RuntimeError(f"Linux runtime requires origin-only RUNPATH: {path.name}")
|
||||
needed = set(metadata.get("NEEDED", []))
|
||||
allowed = all_names | LINUX_SYSTEM_LIBRARIES
|
||||
if path.name == "libggml-vulkan.so":
|
||||
allowed = allowed | {"libvulkan.so.1"}
|
||||
if needed - allowed:
|
||||
raise RuntimeError(f"Unpackaged Linux runtime dependency for {path.name}: {sorted(needed - allowed)}")
|
||||
for source, destination, _ in records:
|
||||
if destination != source:
|
||||
if destination.is_symlink():
|
||||
destination.unlink()
|
||||
shutil.copy2(source, destination)
|
||||
def checksum(path):
|
||||
with path.open("rb") as stream:
|
||||
return hashlib.file_digest(stream, "sha256").hexdigest()
|
||||
inventory = {"platform": "linux", "worker_sha256": checksum(worker),
|
||||
"libraries": [{"path": path.name, "bytes": path.stat().st_size, "sha256": checksum(path)}
|
||||
for _, path, _ in sorted(records)]}
|
||||
temporary = build / "runtime-libraries.json.partial"
|
||||
temporary.write_text(json.dumps(inventory, indent=2), encoding="utf-8")
|
||||
os.replace(temporary, build / "runtime-libraries.json")
|
||||
|
||||
|
||||
def bundle_windows_crt(build):
|
||||
@@ -90,7 +172,7 @@ def main():
|
||||
reverse = subprocess.run(["git", "apply", "--reverse", "--check", str(patch)], cwd=source, capture_output=True)
|
||||
if reverse.returncode:
|
||||
raise RuntimeError("Runtime memory patch cannot be applied or verified")
|
||||
if os.name == "nt" and args.backend != "cpu":
|
||||
if (os.name == "nt" and args.backend != "cpu") or linux_dispatch(args.backend):
|
||||
for patch_name in ["cpu-dispatch.patch", "cpu-only-loader.patch"]:
|
||||
dispatch_patch = Path(__file__).with_name(patch_name)
|
||||
check = subprocess.run(["git", "apply", "--check", str(dispatch_patch)], cwd=source, capture_output=True)
|
||||
@@ -125,6 +207,8 @@ def main():
|
||||
temporary = directory / "runtime-libraries.json.partial"
|
||||
temporary.write_text(json.dumps(inventory, indent=2), encoding="utf-8")
|
||||
os.replace(temporary, directory / "runtime-libraries.json")
|
||||
if linux_dispatch(args.backend):
|
||||
bundle_linux_libraries(build)
|
||||
print(f"Runtime build complete: {build}")
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user