build: package universal Windows runtime and verify automatic CPU fallback

This commit is contained in:
karl committed 2026-10-12 22:03:16 +02:00
1 parent 0fcc8ec21c
commit 278b09f82c
16 files changed
+695 -34

No files matched your search

+8
View File
@@ -22,6 +22,11 @@ def main():
args = parser.parse_args()
if not 1 <= args.jobs <= 16:
parser.error("jobs must be 1–16")
vulkan_loader = None
if os.name == "nt" and args.backend in {"auto", "vulkan"}:
from provision_vulkan import provision
sdk, vulkan_loader = provision()
os.environ["VULKAN_SDK"] = str(sdk)
source = ROOT / ".searchi/references/llama.cpp"
build = ROOT / ".searchi/runtime" / args.backend
if not source.exists():
@@ -51,6 +56,9 @@ def main():
"-DCMAKE_BUILD_TYPE=Release", f"-DLLAMA_CPP_DIR={source}",
f"-DSEARCHI_ACCELERATION={args.backend}"])
run([cmake_exe, "--build", build, "--config", "Release", "--parallel", args.jobs])
if vulkan_loader:
for name in ["vulkan-1.dll", "VULKAN-LICENSE.txt"]:
shutil.copy2(vulkan_loader / name, build / "Release" / name)
print(f"Runtime build complete: {build}")
+84
View File
@@ -0,0 +1,84 @@
"""Verify a real Windows package and its default-mode no-GPU CPU inference.
No desktop UI or GPU inference is exercised. The upstream Vulkan provider is
disabled only inside the child process to expose a genuinely CPU-only registry.
"""
import argparse
import hashlib
import json
import math
import os
from pathlib import Path
import subprocess
import tempfile
import time
import zipfile
ROOT = Path(__file__).resolve().parents[2]
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--archive", type=Path, default=ROOT / ".searchi/packages/searchi-windows.zip")
parser.add_argument("--models", type=Path, default=ROOT / ".searchi/models/embeddinggemma2")
parser.add_argument("--output", type=Path, default=ROOT / ".searchi/packages/smoke.json")
args = parser.parse_args()
manifest = json.loads(Path(__file__).with_name("model-manifest.json").read_text())
model = args.models / manifest["files"][0]["path"]
with model.open("rb") as stream:
assert hashlib.file_digest(stream, "sha256").hexdigest() == manifest["files"][0]["sha256"]
parent = ROOT / ".searchi/packages"
parent.mkdir(parents=True, exist_ok=True)
with tempfile.TemporaryDirectory(prefix="smoke-", dir=parent) as folder:
target = Path(folder).resolve()
assert target.is_relative_to(parent.resolve())
with zipfile.ZipFile(args.archive) as archive:
package = json.loads(archive.read("PACKAGE.json"))
for record in package["files"]:
path = target / record["path"]
assert path.resolve().is_relative_to(target)
data = archive.read(record["path"])
assert len(data) == record["bytes"]
assert hashlib.sha256(data).hexdigest() == record["sha256"]
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(data)
environment = dict(os.environ, GGML_DISABLE_VULKAN="1")
environment.pop("SEARCHI_FORCE_CPU", None)
started = time.perf_counter()
worker = subprocess.Popen([str(target / "searchi-gemma-worker.exe"), "--model", str(model),
"--threads", "1", "--device", "auto"], stdin=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, text=True, encoding="utf-8", env=environment,
creationflags=subprocess.CREATE_NO_WINDOW)
request = json.dumps({"op": "code_query", "text": "retry failed requests with exponential backoff"}) + "\n"
try:
stdout, stderr = worker.communicate(request, timeout=120)
except BaseException:
worker.kill()
worker.wait(timeout=10)
raise
assert worker.returncode == 0, stderr
worker_duration_ms = (time.perf_counter()-started)*1000
hello, result = [json.loads(line) for line in stdout.splitlines()]
assert hello["ready"] and hello["device"] == "cpu" and result["device"] == "cpu" and result["ok"]
vector = result["vectors"][0]
assert len(vector) == 768 and all(math.isfinite(x) for x in vector)
assert abs(sum(x*x for x in vector) - 1) < 0.0001
decoder = subprocess.run([str(target / "media-runtime/bin/ffprobe.exe"), "-version"],
env=dict(os.environ, SEARCHI_FORCE_CPU="1"), capture_output=True, text=True,
creationflags=subprocess.CREATE_NO_WINDOW, check=True, timeout=30)
with args.archive.open("rb") as stream:
archive_checksum = hashlib.file_digest(stream, "sha256").hexdigest()
report = {"architecture": package["architecture"], "verified_files": len(package["files"]),
"archive_sha256": archive_checksum,
"worker_requested_device": "auto", "worker_actual_device": hello["device"],
"no_gpu_condition": "GGML_DISABLE_VULKAN=1 only in child; SEARCHI_FORCE_CPU absent",
"worker_startup_query_and_exit_ms": worker_duration_ms,
"query_dimensions": len(vector), "decoder": decoder.stdout.splitlines()[0],
"scope": "Real packaged worker default-mode CPU fallback with disabled provider; no GPU, native UI or clean-machine certification"}
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(json.dumps(report, indent=2)+"\n")
print(json.dumps(report, indent=2))
if __name__ == "__main__":
main()
+173
View File
@@ -0,0 +1,173 @@
"""Pinned Windows-x64 Vulkan build tools and app-local loader, no GPU execution.
SDK copy-only mode does not install drivers or change system environment/registry.
Checksums are from https://vulkan.lunarg.com/sdk/home/ (2026-10-12).
"""
import hashlib
import json
import os
from pathlib import Path
import platform
import subprocess
import urllib.request
import uuid
import zipfile
ROOT = Path(__file__).resolve().parents[2]
VERSION = "1.4.363.0"
SDK_SHA = "94a82d378f7a5e3e54c9db7d2fb7016af136e14ac0a18dbf0f2f67a36352d141"
LOADER_SHA = "a25a927aa8b9f0371048f1861cf88ac3b9bc9b1fb332c42d897c8ab32695769a"
def digest(path):
with path.open("rb") as stream:
return hashlib.file_digest(stream, "sha256").hexdigest()
def download(path, remote, expected, limit):
if path.is_file() and digest(path) == expected:
return
temporary = path.with_suffix(path.suffix + ".partial")
url = f"https://sdk.lunarg.com/sdk/download/{VERSION}/windows/{remote}?Human=true"
try:
request = urllib.request.Request(url, headers={"User-Agent": "searchi-build-tooling/1.0"})
with urllib.request.urlopen(request, timeout=120) as response, temporary.open("wb") as output:
count = 0
while block := response.read(1024 * 1024):
count += len(block)
if count > limit:
raise ValueError("Vulkan download exceeds size ceiling")
output.write(block)
if digest(temporary) != expected:
raise ValueError("Vulkan download checksum mismatch")
os.replace(temporary, path)
finally:
temporary.unlink(missing_ok=True)
def sdk_files(sdk):
required = [sdk / "Bin/glslc.exe", sdk / "Lib/vulkan-1.lib",
sdk / "Include/vulkan/vulkan.h", sdk / "Include/vulkan/vulkan_core.h"]
if not all(path.is_file() for path in required):
raise RuntimeError("SDK copy did not provide required tools and headers")
files = set(required)
for directory in [sdk / "Include/vulkan", sdk / "Include/vk_video"]:
files.update(path for path in directory.rglob("*") if path.is_file())
files.update((sdk / "Bin").glob("*.dll"))
if len(files) > 4096:
raise ValueError("Vulkan SDK manifest exceeds file count ceiling")
return {path.relative_to(sdk).as_posix(): digest(path) for path in sorted(files)}
def sdk_verified(sdk):
manifest = sdk / "searchi-provenance.json"
try:
if manifest.stat().st_size > 1024 * 1024:
return False
value = json.loads(manifest.read_text(encoding="utf-8"))
return (value.get("version") == VERSION and value.get("installer_sha256") == SDK_SHA
and value.get("files") == sdk_files(sdk))
except (OSError, ValueError, RuntimeError, TypeError, AttributeError):
return False
def write_sdk_provenance(sdk):
value = {"version": VERSION, "installer_sha256": SDK_SHA, "files": sdk_files(sdk)}
temporary = sdk / "searchi-provenance.json.partial"
try:
temporary.write_text(json.dumps(value, sort_keys=True), encoding="utf-8")
os.replace(temporary, sdk / "searchi-provenance.json")
finally:
temporary.unlink(missing_ok=True)
def owned_path(directory, path):
"""Resolve and reject escaped/reparse targets before directory moves."""
owner = directory.resolve()
target = path.resolve()
if target == owner or not target.is_relative_to(owner):
raise ValueError("Vulkan SDK path escapes owned toolchains directory")
return target
def install_sdk(directory, sdk, installer):
# Qt's copy-only installer refuses an existing root. Use a fresh sibling,
# validate it fully, then swap; preserve previous/failed trees for recovery.
sdk = owned_path(directory, sdk)
staging = owned_path(directory, directory / f"vulkan-sdk-{VERSION}-staging-{uuid.uuid4().hex}")
backup = owned_path(directory, directory / f"vulkan-sdk-{VERSION}-backup-{uuid.uuid4().hex}")
if staging.exists() or backup.exists():
raise RuntimeError("Generated Vulkan staging or backup directory already exists")
environment = dict(os.environ, QT_OPENGL="software", SEARCHI_FORCE_CPU="1")
subprocess.run([str(installer), "--root", str(staging), "--accept-licenses", "--default-answer",
"--confirm-command", "install", "copy_only=1"], env=environment, check=True,
creationflags=subprocess.CREATE_NO_WINDOW)
write_sdk_provenance(staging)
if not sdk_verified(staging):
raise RuntimeError("Fresh Vulkan SDK failed provenance validation")
# Re-resolve immediately before mutation; no recursive removal is performed.
owned_path(directory, staging)
owned_path(directory, sdk)
owned_path(directory, backup)
previous = sdk.exists()
if previous:
os.replace(sdk, backup)
try:
os.replace(staging, sdk)
except OSError:
if previous and not sdk.exists():
owned_path(directory, backup)
owned_path(directory, sdk)
os.replace(backup, sdk)
raise
def stage_loader(archive, loader):
if digest(archive) != LOADER_SHA:
raise ValueError("Vulkan runtime archive checksum mismatch")
prefix = f"VulkanRT-X64-{VERSION}-Components/"
members = []
with zipfile.ZipFile(archive) as package:
for source, name in [("x64/vulkan-1.dll", "vulkan-1.dll"),
("VulkanRT-License.txt", "VULKAN-LICENSE.txt")]:
member = package.getinfo(prefix + source)
if member.file_size > 16 * 1024 * 1024:
raise ValueError("Vulkan runtime member exceeds bound")
with package.open(member) as stream:
data = stream.read(16 * 1024 * 1024 + 1)
if len(data) != member.file_size or len(data) > 16 * 1024 * 1024:
raise ValueError("Vulkan runtime member exceeds bound or is truncated")
members.append((name, data))
loader.mkdir(exist_ok=True)
for name, data in members:
destination = loader / name
if destination.is_file() and digest(destination) == hashlib.sha256(data).hexdigest():
continue # Never rewrite a verified DLL mapped by another process.
temporary = destination.with_suffix(destination.suffix + ".partial")
try:
temporary.write_bytes(data)
os.replace(temporary, destination)
finally:
temporary.unlink(missing_ok=True)
def provision():
if os.name != "nt" or platform.machine().lower() not in {"amd64", "x86_64"}:
raise RuntimeError("Automatic Vulkan tooling provisioning is verified for Windows x64 only")
directory = ROOT / ".searchi/toolchains"
directory.mkdir(parents=True, exist_ok=True)
sdk = directory / f"vulkan-sdk-{VERSION}"
if not sdk_verified(sdk):
installer = directory / f"vulkansdk-windows-X64-{VERSION}.exe"
download(installer, installer.name, SDK_SHA, 400 * 1024 * 1024)
install_sdk(directory, sdk, installer)
archive = directory / f"VulkanRT-X64-{VERSION}-Components.zip"
download(archive, "vulkan-runtime-components.zip", LOADER_SHA, 50 * 1024 * 1024)
loader = directory / "vulkan-loader"
stage_loader(archive, loader)
return sdk, loader
if __name__ == "__main__":
sdk, loader = provision()
print(f"Verified SDK: {sdk}\nApp-local loader: {loader}")
+8 -1
View File
@@ -42,17 +42,24 @@ def main():
if args.bundle_model:
manifest = json.loads(Path(__file__).with_name("model-manifest.json").read_text())
artifacts = verify_bundle(ROOT, manifest)
loader = None
if os.name == "nt" and args.backend in {"auto", "vulkan"}:
from provision_vulkan import provision
_, loader = provision()
destination = ROOT / "apps/desktop/src-tauri/resources"
destination.mkdir(parents=True, exist_ok=True)
shutil.copy2(source, destination / name)
# Include the upstream runtime license with the statically linked helper.
shutil.copy2(license_path, destination / "LLAMA-LICENSE.txt")
if loader:
for name in ["vulkan-1.dll", "VULKAN-LICENSE.txt"]:
shutil.copy2(loader / name, destination / name)
if args.bundle_model:
model_destination = destination / "embeddinggemma2"
model_destination.mkdir(exist_ok=True)
for artifact in artifacts:
shutil.copy2(artifact, model_destination / artifact.name)
print(f"Staged native worker: {destination / name}")
print(f"Staged native worker: {destination / source.name}")
if __name__ == "__main__":
@@ -0,0 +1,145 @@
import hashlib
import io
from pathlib import Path
import tempfile
import subprocess
import unittest
import zipfile
from unittest.mock import patch
import provision_vulkan as provisioning
from provision_vulkan import download, sdk_verified, write_sdk_provenance, stage_loader
class DownloadTests(unittest.TestCase):
@staticmethod
def fake_sdk(sdk):
for name in ["Bin/glslc.exe", "Lib/vulkan-1.lib", "Include/vulkan/vulkan.h",
"Include/vulkan/vulkan_core.h"]:
path = sdk / name
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(name.encode())
def test_fresh_install_preserves_previous_cache_and_uses_copy_only(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
sdk = root / "sdk"
sdk.mkdir()
(sdk / "old-marker").write_bytes(b"previous tree")
def install(command, **options):
fresh = Path(command[command.index("--root") + 1])
self.assertFalse(fresh.exists())
self.assertIn("copy_only=1", command)
self.assertEqual(options["env"]["QT_OPENGL"], "software")
self.assertEqual(options["env"]["SEARCHI_FORCE_CPU"], "1")
self.fake_sdk(fresh)
with patch("provision_vulkan.subprocess.run", side_effect=install):
provisioning.install_sdk(root, sdk, root / "installer.exe")
self.assertTrue(sdk_verified(sdk))
backups = list(root.glob("*-backup-*"))
self.assertEqual(len(backups), 1)
self.assertEqual((backups[0] / "old-marker").read_bytes(), b"previous tree")
def test_failed_installer_and_failed_swap_preserve_previous_provenance(self):
for failure in ["installer", "swap"]:
with self.subTest(failure=failure), tempfile.TemporaryDirectory() as directory:
root = Path(directory)
sdk = root / "sdk"
self.fake_sdk(sdk)
write_sdk_provenance(sdk)
old_manifest = (sdk / "searchi-provenance.json").read_bytes()
def install(command, **options):
if failure == "installer":
raise subprocess.CalledProcessError(1, command)
self.fake_sdk(Path(command[command.index("--root") + 1]))
real_replace = provisioning.os.replace
def replace(source, destination):
if failure == "swap" and "-staging-" in Path(source).name and Path(destination) == sdk:
raise PermissionError("injected swap failure")
real_replace(source, destination)
with patch("provision_vulkan.subprocess.run", side_effect=install), \
patch("provision_vulkan.os.replace", side_effect=replace):
with self.assertRaises((OSError, subprocess.CalledProcessError)):
provisioning.install_sdk(root, sdk, root / "installer.exe")
self.assertEqual((sdk / "searchi-provenance.json").read_bytes(), old_manifest)
self.assertTrue(sdk_verified(sdk))
def test_install_rejects_target_outside_owned_directory(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
owned = root / "owned"
owned.mkdir()
with patch("provision_vulkan.subprocess.run", side_effect=AssertionError("installer ran")):
with self.assertRaisesRegex(ValueError, "escapes owned"):
provisioning.install_sdk(owned, root / "outside", root / "installer.exe")
def test_sdk_requires_provenance_and_detects_header_corruption(self):
with tempfile.TemporaryDirectory() as directory:
sdk = Path(directory)
for name in ["Bin/glslc.exe", "Lib/vulkan-1.lib", "Include/vulkan/vulkan.h",
"Include/vulkan/vulkan_core.h", "Include/vk_video/video.h"]:
path = sdk / name
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(name.encode())
self.assertFalse(sdk_verified(sdk))
write_sdk_provenance(sdk)
self.assertTrue(sdk_verified(sdk))
(sdk / "Include/vk_video/video.h").write_bytes(b"corrupt")
self.assertFalse(sdk_verified(sdk))
def test_verified_loader_is_not_replaced_and_bad_archive_preserves_it(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
archive = root / "runtime.zip"
prefix = f"VulkanRT-X64-{provisioning.VERSION}-Components/"
with zipfile.ZipFile(archive, "w") as package:
package.writestr(prefix + "x64/vulkan-1.dll", b"verified loader")
package.writestr(prefix + "VulkanRT-License.txt", b"license")
package.writestr("../outside", b"ignored")
checksum = provisioning.digest(archive)
loader = root / "loader"
with patch.object(provisioning, "LOADER_SHA", checksum):
stage_loader(archive, loader)
with patch("provision_vulkan.os.replace", side_effect=AssertionError("rewrote loader")):
stage_loader(archive, loader)
archive.write_bytes(b"corrupt")
with self.assertRaisesRegex(ValueError, "archive checksum"):
stage_loader(archive, loader)
self.assertEqual((loader / "vulkan-1.dll").read_bytes(), b"verified loader")
self.assertFalse((root / "outside").exists())
def test_unsupported_architecture_fails_before_filesystem_or_network(self):
with patch("provision_vulkan.platform.machine", return_value="ARM64"), \
patch("provision_vulkan.Path.mkdir", side_effect=AssertionError("filesystem")), \
patch("provision_vulkan.urllib.request.urlopen", side_effect=AssertionError("network")):
with self.assertRaisesRegex(RuntimeError, "Windows x64 only"):
provisioning.provision()
def test_verified_cache_needs_no_network(self):
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / "sdk.exe"
path.write_bytes(b"verified SDK")
checksum = hashlib.sha256(path.read_bytes()).hexdigest()
with patch("provision_vulkan.urllib.request.urlopen", side_effect=AssertionError("unexpected network")):
download(path, "sdk.exe", checksum, 100)
def test_corrupt_or_oversized_download_preserves_previous_file(self):
for payload, limit, reason in [(b"wrong", 100, "checksum"), (b"too large", 2, "size ceiling")]:
with self.subTest(reason=reason), tempfile.TemporaryDirectory() as directory:
path = Path(directory) / "sdk.exe"
path.write_bytes(b"previous cached file")
with patch("provision_vulkan.urllib.request.urlopen", return_value=io.BytesIO(payload)):
with self.assertRaisesRegex(ValueError, reason):
download(path, "sdk.exe", hashlib.sha256(b"expected").hexdigest(), limit)
self.assertEqual(path.read_bytes(), b"previous cached file")
self.assertFalse(path.with_suffix(".exe.partial").exists())
if __name__ == "__main__":
unittest.main()