"""Load existing bank and stock artifacts with one explicitly selected runtime.""" import hashlib from pathlib import Path from typing import TYPE_CHECKING, cast from openpilot.starpilot.models.runtime_bridge.bootstrap import LEGACY_REVISION, activate if TYPE_CHECKING: from tinygrad.uop.ops import UOp from openpilot.starpilot.models.runtime_bridge.legacy_types import LegacyBuffer, LegacyBufferFactory, LegacyHCQModule, LegacyPersistentUnpickler BUNDLE = Path(__file__).resolve().parents[4] / 'tinygrad_model_runtime/legacy9d' MANIFEST_SHA256 = "20ca9e77010c1ab788f2adb824103db3d595b0da8c4069d6e32b1266f7977b1f" _GUARD_HASHES = { 'libbounded_wait.so': '24ed559861c8636c1026cad9a244854bb2236b5ee8b91bf21c5efbf688fa4a69', 'libusb_guards.so': '66a3dfe48ac17899862ab80160cf6dac2c9b9707234527ba0d808f3b68404114', } _POLICY_SHA256 = 'ff9a762b130207dcf9919714bccd7aabc6d3f6e9d707e0b3a0dbd7fce5858aed' _selection = None _protection = None def prepare(role): global _selection if _selection is not None and _selection.get('purpose') == 'compiler': raise RuntimeError('A model compiler cannot become an inference child') from openpilot.common.hardware import AGNOS if not AGNOS: return None if _selection is not None: if _selection['role'] != role: raise RuntimeError('A model child cannot change its execution role') return _selection _verify_policy() cache = Path('/data/starpilot-model-runtime') _selection = activate(BUNDLE, MANIFEST_SHA256, LEGACY_REVISION, role, cache) return _selection def prepare_compiler(): """Keep newly compiled bank artifacts on their declared compiler ABI.""" global _selection if _selection is not None: raise RuntimeError('Select the compiler runtime before model inference') import tempfile selection = activate(BUNDLE, MANIFEST_SHA256, LEGACY_REVISION, 'driving', Path(tempfile.gettempdir()).resolve() / 'starpilot-model-compiler') selection['purpose'] = 'compiler' _selection = selection return _selection def require_compiler(): if _selection is None or _selection.get('purpose') != 'compiler': raise RuntimeError('Compile bank artifacts through the selected compiler entry point') def _verify_policy(): root = Path(__file__).parent policy_path = root / 'guard-policy.json' if policy_path.is_symlink() or not policy_path.is_file(): raise RuntimeError('Missing model submission guard policy') raw = policy_path.read_bytes() if hashlib.sha256(raw).hexdigest() != _POLICY_SHA256: raise RuntimeError('Model submission guard policy changed') import json policy = json.loads(raw) if (policy['compiler_revision'] != LEGACY_REVISION or policy['runtime_manifest_sha256'] != MANIFEST_SHA256 or policy['native_abi'] != 'linux-aarch64-64bit' or set(policy['files']) != {'bootstrap.py', 'qcom.py', 'usb.py', 'capture.py', 'bounded_wait.c', 'usb_guards.c', 'libbounded_wait.so', 'libusb_guards.so'}): raise RuntimeError('Model submission guard identity mismatch') for name, expected in policy['files'].items(): path = root / name if path.is_symlink() or not path.is_file() or hashlib.sha256(path.read_bytes()).hexdigest() != expected: raise RuntimeError('Model submission guard source/artifact changed: ' + name) def _library(name): path = Path(__file__).with_name(name) if path.is_symlink() or not path.is_file() or hashlib.sha256(path.read_bytes()).hexdigest() != _GUARD_HASHES[name]: raise RuntimeError('Model submission guard artifact changed: ' + name) return path def selected_legacy(): return _selection is not None def _guard(chestnut): global _protection if _selection is None: raise RuntimeError('Select the model runtime before loading a device artifact') if _selection.get('purpose') == 'compiler': raise RuntimeError('A model compiler cannot load inference artifacts') if chestnut and _selection['role'] != 'driving': raise ValueError('Driver monitoring requires the QCOM runtime') if _protection is None: _verify_policy() # Driving can load its verified QCOM fallback before attempting Chestnut. if _selection['role'] == 'driving': from openpilot.starpilot.models.runtime_bridge.usb import USBProtection candidate = USBProtection(_library('libbounded_wait.so'), _library('libusb_guards.so')) candidate.install_fresh_guard() else: from openpilot.starpilot.models.runtime_bridge.qcom import Protection from tinygrad.runtime.support import hcq2 from tinygrad.uop.ops import Ops, ProgramInfo candidate = Protection(_library('libbounded_wait.so')) legacy_hcq = cast("LegacyHCQModule", hcq2) original = legacy_hcq.lower_and_compile def checked(linear: "UOp") -> "UOp": if any(u.op is Ops.CALL and (u.body.op is Ops.SINK or (u.body.op is Ops.PROGRAM and not (isinstance(u.body.arg, ProgramInfo) and u.body.src and u.body.src[-1].op is Ops.BINARY))) for u in linear.toposort()): raise ValueError('Unexpected fresh monitoring submission compilation') return original(linear) legacy_hcq.lower_and_compile = checked _protection = candidate return _protection _STOCK_SHA256 = { 'dmonitoring_model_tinygrad.pkl': '7f7198c63d2d9b0d015cc860fdd836db50a4b9a75ab5d56cffd4d4eee5eb7b97', 'dm_warp_1928x1208_tinygrad.pkl': '5711934a11792e539e72ce68837b8bab802ee7bd2a909402fd35a4cc4efc382f', 'dm_warp_1344x760_tinygrad.pkl': '79df4a2f9701aea9cf6c21477cae07852d7d8c245737599e6b72a1e6fa4fe88f', } def _stock_bytes(path): import os import stat path = Path(path) expected = _STOCK_SHA256.get(path.name.removesuffix('.unchunked')) if expected is None: raise ValueError('Unqualified stock model artifact') fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW | os.O_CLOEXEC) with os.fdopen(fd, 'rb') as source: before = os.fstat(source.fileno()) if not stat.S_ISREG(before.st_mode) or not 0 < before.st_size <= 20_768_359: raise ValueError('Unexpected stock artifact file') data = source.read(20_768_360) after = os.fstat(source.fileno()) def identity(value): return value.st_dev, value.st_ino, value.st_size, value.st_mtime_ns, value.st_ctime_ns if (identity(before) != identity(after) or len(data) != before.st_size or hashlib.sha256(data).hexdigest() != expected): raise ValueError('Stock model artifact changed') return data def _load_monitoring(data): # The bounded stock artifact is already authenticated. Consume those exact bytes; # the DISK loader reopens paths and requests writable mmap, so it cannot use sealed files. import io import pickle import struct from tinygrad import Device from tinygrad.device import Buffer from tinygrad.dtype import dtypes if len(data) < 8: raise ValueError('Missing monitoring pickle envelope') opcode_size = struct.unpack('