Compile native Chestnut models

This commit is contained in:
firestar5683
2026-10-23 16:17:25 -05:00
parent 3a1d0bc615
commit 26bc78b69d
38 changed files with 4522 additions and 152 deletions
+29 -3
View File
@@ -6,7 +6,7 @@ from SCons.Script import Action, Value
from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye
from openpilot.common.transformations.model import MEDMODEL_INPUT_SIZE, DM_INPUT_SIZE
from openpilot.system.camerad.cameras.nv12_info import get_nv12_info
from tools.laptop_device_build.qcom_artifact_contract import import_artifact
from tools.laptop_device_build.qcom_artifact_contract import import_artifact, native_records, verify_native_defaults
Import('env', 'arch')
@@ -35,6 +35,31 @@ laptop_qcom_import = arch == 'comma_arm64' and os.getenv('COMMA_LAPTOP_CROSS_BUI
qcom_package = Dir('#.cache/qcom-artifact-package').abspath
# Native defaults are release artifacts, not outputs of the legacy model compiler.
# Validate their current source/vendor/guard contract without regenerating them.
native_bundle = native_records(Path(Dir('#').abspath)) if arch == 'comma_arm64' else None
if native_bundle is not None:
native_root = Path(Dir('#').abspath)
native_bridge = native_root / 'openpilot/starpilot/models/runtime_bridge'
native_sources = [native_bridge / name for name in (
'native.py', 'native-policy.json', 'native_guards.py', 'bounded_wait.c', 'usb_guards.c',
'libbounded_wait.so', 'libusb_guards.so')]
native_sources += [native_bridge.parent / 'catalog-native02e.json']
for name in native_bundle:
base = native_root / 'openpilot/selfdrive/modeld/models' / name
native_sources += [path for path in (base, *base.parent.glob(base.name + '.chunk*')) if path.is_file()]
def verify_native_bundle(target, source, env):
records = verify_native_defaults(native_root, prefer_regular=True)
stamp = Path(str(target[0]))
stamp.parent.mkdir(parents=True, exist_ok=True)
stamp.write_text(str({name: row['artifact_sha256'] for name, row in records.items()}) + '\n')
native_node = lenv.Command('#.cache/native-model-defaults/verified', [File(str(path)) for path in native_sources],
Action(verify_native_bundle, ' [NATIVE MODEL VERIFY] $TARGET'))
lenv.AlwaysBuild(native_node)
def model_action(command):
if not laptop_qcom_import:
return Action(command, " [ONNX] $TARGET")
@@ -77,7 +102,8 @@ def compile_model(onnx_path, pkl_path):
if laptop_qcom_import:
lenv.AlwaysBuild(node)
compile_model('models/dmonitoring_model.onnx', 'models/dmonitoring_model_tinygrad.pkl')
if native_bundle is None:
compile_model('models/dmonitoring_model.onnx', 'models/dmonitoring_model_tinygrad.pkl')
model_w, model_h = MEDMODEL_INPUT_SIZE
for cam_w, cam_h in camera_configs:
@@ -92,7 +118,7 @@ for cam_w, cam_h in camera_configs:
lenv.AlwaysBuild(node)
dm_w, dm_h = DM_INPUT_SIZE
for cam_w, cam_h in camera_configs:
for cam_w, cam_h in (() if native_bundle is not None else camera_configs):
dm_pkl_path = File(f"models/dm_warp_{cam_w}x{cam_h}_tinygrad.pkl").abspath
stride, y_height, uv_height, frame_size = get_nv12_info(cam_w, cam_h)
cmd = (f'{tg_flags} {mac_brew_string} python3 "{compiler}/compile_warp.py" '
@@ -47,8 +47,8 @@ class ModelState:
self.model_run = jits['run']
self.outputs = {name: Tensor(np.zeros(shape, dtype=dtype), device=device).realize() for name, (shape, dtype, device) in jits['output_specs'].items()}
warp_path = MODELS_DIR / f'dm_warp_{cam_w}x{cam_h}_tinygrad.pkl'
from openpilot.starpilot.models.runtime_bridge.runtime import selected_legacy, load_warp
if selected_legacy():
from openpilot.starpilot.models.runtime_bridge.runtime import prepared, load_warp
if prepared():
self.image_warp = load_warp(warp_path)
else:
with warp_path.open('rb') as f:
+2 -2
View File
@@ -15,9 +15,9 @@ def modeld_pkl_path(chestnut: bool):
return materialize_file_chunked(path) if path.is_file() or Path(f'{path}.chunkmanifest').exists() else path
def load_oob(path, chestnut=False):
from openpilot.starpilot.models.runtime_bridge.runtime import selected_legacy, load_oob as protected_load
from openpilot.starpilot.models.runtime_bridge.runtime import prepared, load_oob as protected_load
if AGNOS:
if not selected_legacy():
if not prepared():
raise RuntimeError('Prepare the model runtime before loading a device artifact')
return protected_load(materialize_file_chunked(path), chestnut)
from tinygrad import Context
+8 -8
View File
@@ -8,8 +8,9 @@ from functools import cached_property
import os
os.environ['GMMU'] = '0' # for chestnut fast loading, noop for qcom
os.environ.setdefault('AM_POWER_LIMIT', '100')
from openpilot.starpilot.models.runtime_bridge.runtime import prepare as prepare_model_runtime, selected_legacy
from openpilot.starpilot.models.runtime_bridge.runtime import prepare as prepare_model_runtime, selected_legacy, selected_plan
prepare_model_runtime('driving')
MODEL_RUNTIME_PLAN = selected_plan()
from tinygrad.device import Buffer, Device
from tinygrad.dtype import DType, dtypes
from tinygrad.engine.realize import lower_and_compile
@@ -50,7 +51,7 @@ from openpilot.starpilot.models.status import ModelVariant
from openpilot.starpilot.models.startup import report_load_timeout, wait_for_chestnut_power
from openpilot.starpilot.models.recovery import STARTUP_FALLBACK_EXIT
from openpilot.starpilot.navigation.intent import TurnIntent, matching_turn_signal
from openpilot.starpilot.models.catalog import BUNDLED_CURRENT, BY_ID, DEFAULT_SMALL, DEFAULT_SMALL_SHA256
from openpilot.starpilot.models.catalog import BUNDLED_CURRENT, DEFAULT_SMALL
from openpilot.starpilot.models.runner import CatalogModelState, load_verified_model
from openpilot.starpilot.models import jetlink_adapter
import uuid
@@ -290,8 +291,8 @@ def main(demo=False):
# a Jetson, PC or phone on the port looks like a chestnut to cable_connected(): the cable counts with Jetlink off only
chestnut_available = chestnut_present() or (cable_connected() and not jetlink_adapter.link_on())
recovery_small_only = os.getenv("STARPILOT_MODELD_RECOVERY_SMALL_ONLY") == "1"
selection = resolve_runtime(chestnut_available=chestnut_available and not recovery_small_only, randomize=not recovery_small_only)
requested_model_id = requested_runtime_id(chestnut_available)
selection = resolve_runtime(chestnut_available=chestnut_available and not recovery_small_only, plan=MODEL_RUNTIME_PLAN)
requested_model_id = requested_runtime_id(chestnut_available, plan=MODEL_RUNTIME_PLAN)
CHESTNUT = selection.allow_big and selection.big_path is not None
if CHESTNUT:
from tinygrad.runtime.support.am import startup_trace
@@ -350,13 +351,12 @@ def main(demo=False):
return selected, prepared, False
except Exception:
cloudlog.exception("selected small model load failed, using shipped RDFv4")
from openpilot.starpilot.models.manager import shipped_default
path = shipped_default()
path = selection.fallback_path
if path is None:
raise RuntimeError("Shipped RDFv4 is missing or corrupt")
fallback, prepared = load_verified_model(vipc_client_main.width, vipc_client_main.height,
path, BY_ID[DEFAULT_SMALL].version, False,
DEFAULT_SMALL, DEFAULT_SMALL_SHA256)
path, selection.fallback_version, False,
DEFAULT_SMALL, selection.fallback_sha256)
return fallback, prepared, selection.small_id != DEFAULT_SMALL
# Preload a custom fallback before the GPU worker takes the catalog lock.
@@ -0,0 +1,18 @@
{
"bytes": 125895084,
"sha256": "e23f65b3790a2092348024603966717506c2ad1fae10b4312bdfe1f2b932df61",
"parts": [
{
"bytes": 47185920,
"sha256": "268527c074b659a76c495d91afac8aee21e12100cc0191daa116f0db633b9165"
},
{
"bytes": 47185920,
"sha256": "8622fdec72524dbc80c744fa17b92edea59d5889f0e1c4d02d3e4e8d44521d2b"
},
{
"bytes": 31523244,
"sha256": "9cff2854787bf48fd7fd0f64a4a243a0011ed76cc3c09fb3399c0d3d9661d1d1"
}
]
}
@@ -1,10 +1,10 @@
{
"bytes": 20768359,
"sha256": "7f7198c63d2d9b0d015cc860fdd836db50a4b9a75ab5d56cffd4d4eee5eb7b97",
"bytes": 20778338,
"sha256": "2dfdf58712f29be92d37f0b6b06d6ae78626831c3b9e7a0c990d40e8ef84b3a2",
"parts": [
{
"bytes": 20768359,
"sha256": "7f7198c63d2d9b0d015cc860fdd836db50a4b9a75ab5d56cffd4d4eee5eb7b97"
"bytes": 20778338,
"sha256": "2dfdf58712f29be92d37f0b6b06d6ae78626831c3b9e7a0c990d40e8ef84b3a2"
}
]
}
@@ -1,18 +1,18 @@
{
"bytes": 125895084,
"sha256": "e23f65b3790a2092348024603966717506c2ad1fae10b4312bdfe1f2b932df61",
"bytes": 114218310,
"sha256": "efc9c780a824c798c629776601dc7b26aaa1e1270e048d9f90dd2f164de380a9",
"parts": [
{
"bytes": 47185920,
"sha256": "268527c074b659a76c495d91afac8aee21e12100cc0191daa116f0db633b9165"
"sha256": "3e6df8f84030d138d5fdee925b8eeb80bc33468c3406340fd2a026e86ec51b33"
},
{
"bytes": 47185920,
"sha256": "8622fdec72524dbc80c744fa17b92edea59d5889f0e1c4d02d3e4e8d44521d2b"
"sha256": "8a538d5122992ad2d137164a23df0be5fd322d4f6cbb1c9a4561b903ace1420d"
},
{
"bytes": 31523244,
"sha256": "9cff2854787bf48fd7fd0f64a4a243a0011ed76cc3c09fb3399c0d3d9661d1d1"
"bytes": 19846470,
"sha256": "2021d5b79a036efb899ac7423f3c84ec5e59b8ab2859c01402bcba757200767e"
}
]
}
@@ -0,0 +1,188 @@
{
"artifact_abi": "tinygrad_single_v2_arena",
"compiler_revision": "02e698160eed1751738212144a7ea2cfb160b3f1",
"generation": "native02e-v1",
"models": [
{
"artifact_chunk_count": 18,
"artifact_format": "tinygrad_single_v2_arena",
"artifact_sha256": "868b3e775ab92e223a1618e5b9f2690976cbdb61e27516e50a43db96375586b8",
"artifact_size": 804719273,
"community_favorite": false,
"id": "traintrack",
"name": "Train Track \ud83d\udc40\ud83d\udce1",
"qualification_scope": "Native ONNX compilation with original five-frame capture/reload checks; exact retained GPU kernels and weights during bounded-guard preparation; fresh final-source guarded CatalogModelState/Parser runner, four seeded frames at 1928x1208. No physical acceptance or startup speedup asserted.",
"qualification_sha256": "3d7f485199fa8532b41b2d1ce3e484718bffb4c17a587a18b86ceee24ebbf5ff",
"released": "2026-10-17",
"series": "OP Series",
"source_bytes": 792467570,
"source_sha256": "ceb38c593005b3338aee4ca057d5ad9ee64c58f8fdba0b41641a9d4cc6c36e36",
"uses_external_gpu": true,
"version": "v16"
},
{
"artifact_chunk_count": 18,
"artifact_format": "tinygrad_single_v2_arena",
"artifact_sha256": "6a9ccfb1d9c6a518435cddc13e990bd1c89bc87f2d3a5591a8e1a69d4cd552e2",
"artifact_size": 804719273,
"community_favorite": false,
"id": "mountaindewv3",
"name": "Mountain Dew v3 \ud83d\udc40\ud83d\udce1",
"qualification_scope": "Native ONNX compilation with original five-frame capture/reload checks; exact retained GPU kernels and weights during bounded-guard preparation; fresh final-source guarded CatalogModelState/Parser runner, four seeded frames at 1344x760. No physical acceptance or startup speedup asserted.",
"qualification_sha256": "ac3637cb238f9599a43f77144bc332be6930627d77bad098acbcfd6f1d777748",
"released": "2026-10-14",
"series": "OP Series",
"source_bytes": 792542056,
"source_sha256": "1cfd2c45353d876b5b257b24d77537de58ab90a44ae595f15b1f64b6ea3fd770",
"uses_external_gpu": true,
"version": "v16"
},
{
"artifact_chunk_count": 18,
"artifact_format": "tinygrad_single_v2_arena",
"artifact_sha256": "d4bd838f4cfe2465c2ecac3efa32aeeddd9d1a3c0ed80b1987ee87a1a3470358",
"artifact_size": 804718917,
"community_favorite": false,
"id": "mountaindewv2",
"name": "Mountain Dew v2 \ud83d\udc40\ud83d\udce1",
"qualification_scope": "Native ONNX compilation with original five-frame capture/reload checks; exact retained GPU kernels and weights during bounded-guard preparation; fresh final-source guarded CatalogModelState/Parser runner, four seeded frames at 1344x760. No physical acceptance or startup speedup asserted.",
"qualification_sha256": "5b79ede3cf0809fce8497bb7f8feca86be6f387c68c0e706177ed73068927683",
"released": "2026-10-10",
"series": "OP Series",
"source_bytes": 792430559,
"source_sha256": "87fe86ca0813c099efddf9173ba503be2e40ea4c5362c36812a44141f4cb07d4",
"uses_external_gpu": true,
"version": "v16"
},
{
"artifact_chunk_count": 18,
"artifact_format": "tinygrad_single_v2_arena",
"artifact_sha256": "787b13a040362f4efcfca15bf95f7a36694ba21fb8175d8557ed579ceffde282",
"artifact_size": 804719273,
"community_favorite": false,
"id": "mountaindewv1",
"name": "Mountain Dew v1 \ud83d\udc40\ud83d\udce1",
"qualification_scope": "Native ONNX compilation with original five-frame capture/reload checks; exact retained GPU kernels and weights during bounded-guard preparation; fresh final-source guarded CatalogModelState/Parser runner, four seeded frames at 1344x760. No physical acceptance or startup speedup asserted.",
"qualification_sha256": "497a0f262de83a911ba4dbb1d63f9ffbf51567e155574e72170f94949cafa88d",
"released": "2026-10-08",
"series": "OP Series",
"source_bytes": 792487890,
"source_sha256": "c392ba1aa1b4b89e850366024fa6c1beac1f1fdc3a7182e3c3b0234c55e08eeb",
"uses_external_gpu": true,
"version": "v16"
},
{
"artifact_chunk_count": 18,
"artifact_format": "tinygrad_single_v2_arena",
"artifact_sha256": "10aa579f2181154996afc04f30337a7b42c7768f92d2bbdc92d8685acbd725ca",
"artifact_size": 804718766,
"community_favorite": false,
"id": "resfit",
"name": "ResFit",
"qualification_scope": "Native ONNX compilation with original five-frame capture/reload checks; exact retained GPU kernels and weights during bounded-guard preparation; fresh final-source guarded CatalogModelState/Parser runner, four seeded frames at 1344x760. No physical acceptance or startup speedup asserted.",
"qualification_sha256": "cb84da0893e25cfec8f581c7c043949a0f6a830ab9e4745d2f7ab0e8b9e86d43",
"released": "2026-10-06",
"series": "OP Series",
"source_bytes": 792485215,
"source_sha256": "1563b85f6bd00d9e2edf50bdb69646f0d95d5ac0f218d5abe3fe54a13427d71b",
"uses_external_gpu": true,
"version": "v16"
},
{
"artifact_chunk_count": 17,
"artifact_format": "tinygrad_single_v2_arena",
"artifact_sha256": "d4613485c31cc28ab9b75fc8c14072354586c83e3e799e94682dcf06026b8f80",
"artifact_size": 778866789,
"community_favorite": false,
"id": "cinquev3",
"name": "Cinque v3 \ud83d\udc40\ud83d\udce1",
"qualification_scope": "Native ONNX compilation with original five-frame capture/reload checks; exact retained GPU kernels and weights during bounded-guard preparation; fresh final-source guarded CatalogModelState/Parser runner, four seeded frames at 1344x760. No physical acceptance or startup speedup asserted.",
"qualification_sha256": "b169cef607dbb650f055ef87ace3c76f43013da3798ea3d3af384ae2b95026e5",
"released": "2026-09-18",
"series": "OP Series",
"source_bytes": 766354845,
"source_sha256": "404a18cfd86d29637d20c697dfde245bb47c666ae016730ab674c65f4d1e1aa4",
"uses_external_gpu": true,
"version": "v16"
},
{
"artifact_chunk_count": 3,
"artifact_format": "tinygrad_single_v2_arena",
"artifact_sha256": "efc9c780a824c798c629776601dc7b26aaa1e1270e048d9f90dd2f164de380a9",
"artifact_size": 114218310,
"community_favorite": false,
"id": "rdf43",
"model_lab_eligible": true,
"model_size": "small",
"name": "Regret Driven Framework V4 \ud83d\udc40\ud83d\udce1",
"qualification_scope": "Native ONNX compilation with original five-frame capture/reload checks; exact retained GPU kernels and weights during bounded-guard preparation; fresh final-source guarded CatalogModelState/Parser runner, four seeded frames at 1344x760. No physical acceptance or startup speedup asserted.",
"qualification_sha256": "0d0ff59d57da106f042ec79636c65dbcd805d4d15c81a79ae9a7551b46dc5ecd",
"released": "2026-08-10",
"series": "OP Series",
"source_bytes": 96636386,
"source_sha256": "b9550d192e32769f1fca82b2e2f2fae65e335b9f6fbbda2b455fcc8b2207bef6",
"version": "v15"
}
],
"status": "QUALIFIED_NATIVE_RELEASE",
"stock": {
"dm_warp_1344x760_tinygrad.pkl": {
"artifact_sha256": "095467b3397526455aba3573e2458d960cbe195e23f8f91d46ef5e886074006c",
"artifact_size": 1397668,
"qualification_scope": "Actual four-frame driver-monitoring ModelState and both camera warps; 88 output arrays and state histories exact per camera against raw native captures. No road acceptance or speedup asserted.",
"qualification_sha256": "6d9b4ad05d673a352f152b2b6327bcff6de6dbc76c3c96204fb839b67b920fc0",
"source_geometry": {
"border_fill": 16,
"camera": [
1344,
760
],
"layout": "luma",
"nv12": [
1408,
768,
384,
2428928
],
"transform_device": "NPY",
"warp_to": [
1440,
960
]
}
},
"dm_warp_1928x1208_tinygrad.pkl": {
"artifact_sha256": "05021aae07d80fe8ccdd593581398e6432c52d7132384f706c0e2f75d69a7a3b",
"artifact_size": 1397620,
"qualification_scope": "Actual four-frame driver-monitoring ModelState and both camera warps; 88 output arrays and state histories exact per camera against raw native captures. No road acceptance or speedup asserted.",
"qualification_sha256": "6d9b4ad05d673a352f152b2b6327bcff6de6dbc76c3c96204fb839b67b920fc0",
"source_geometry": {
"border_fill": 16,
"camera": [
1928,
1208
],
"layout": "luma",
"nv12": [
2048,
1216,
608,
4804608
],
"transform_device": "NPY",
"warp_to": [
1440,
960
]
}
},
"dmonitoring_model_tinygrad.pkl": {
"artifact_sha256": "2dfdf58712f29be92d37f0b6b06d6ae78626831c3b9e7a0c990d40e8ef84b3a2",
"artifact_size": 20778338,
"qualification_scope": "Actual four-frame driver-monitoring ModelState and both camera warps; 88 output arrays and state histories exact per camera against raw native captures. No road acceptance or speedup asserted.",
"qualification_sha256": "6d9b4ad05d673a352f152b2b6327bcff6de6dbc76c3c96204fb839b67b920fc0",
"source_bytes": 7844499,
"source_sha256": "dee5a294e8afaacc9295ac5d100e00733ecac278e79264b96331e40a3ede1b04"
}
}
}
+17 -10
View File
@@ -1,10 +1,12 @@
"""Reviewed local driving-model catalog. This module never loads model artifacts."""
from dataclasses import dataclass, replace
from dataclasses import dataclass
from functools import lru_cache
import json
from pathlib import Path
from openpilot.starpilot.models.runtime_selection import NATIVE_ABI, NATIVE_REVISION
BUNDLED_CURRENT = "bundled-current"
DEFAULT_SMALL = "rdf43"
@@ -14,6 +16,7 @@ GENERATION = "v26"
COMPILER_REVISION = "9d0446a4ba8a532c8b674fb6ad795af015cd9dcf"
ARTIFACT_ABI = "tinygrad_single_v1_arena"
CATALOG_PATH = Path(__file__).with_name("catalog-v26.json")
NATIVE_CATALOG_PATH = Path(__file__).with_name("catalog-native02e.json")
@dataclass(frozen=True)
@@ -33,10 +36,10 @@ BUNDLED = (
ModelEntry(
model_id=BUNDLED_CURRENT,
name="Regret Driven Framework V4",
source_sha256=DEFAULT_SMALL_SHA256,
source_sha256="",
source_path="openpilot/selfdrive/modeld/models/rdf43_driving_tinygrad.pkl",
compiler_revision="9d0446a4ba8a532c8b674fb6ad795af015cd9dcf",
artifact_abi=ARTIFACT_ABI,
compiler_revision=NATIVE_REVISION,
artifact_abi=NATIVE_ABI,
selectable=True,
version="v15",
),
@@ -44,9 +47,10 @@ BUNDLED = (
CATALOG = BUNDLED + tuple(ModelEntry(
model_id=m["id"], name=m["name"],
source_sha256=DEFAULT_SMALL_SHA256 if m["id"] == DEFAULT_SMALL else "",
source_sha256="",
source_path="openpilot/selfdrive/modeld/models/rdf43_driving_tinygrad.pkl" if m["id"] == DEFAULT_SMALL else "",
compiler_revision=COMPILER_REVISION, artifact_abi=ARTIFACT_ABI, selectable=True,
compiler_revision=NATIVE_REVISION if m["id"] == DEFAULT_SMALL else COMPILER_REVISION,
artifact_abi=NATIVE_ABI if m["id"] == DEFAULT_SMALL else ARTIFACT_ABI, selectable=True,
version=m["version"], uses_external_gpu=bool(m.get("uses_external_gpu", False)),
) for m in json.loads(CATALOG_PATH.read_text())["models"])
@@ -66,9 +70,11 @@ def _cached_entries(root: Path, identities: tuple) -> tuple[ModelEntry, ...]:
from openpilot.starpilot.models.manager import catalog
entries = dict(BY_ID)
for model_id, row in catalog(root).items():
entries[model_id] = (replace(BY_ID[model_id], name=row["name"]) if model_id in BY_ID else
ModelEntry(model_id, row["name"], "", "", COMPILER_REVISION, ARTIFACT_ABI,
True, row["version"], row.get("uses_external_gpu", False)))
entries[model_id] = ModelEntry(model_id, row["name"],
row.get("artifact_sha256", "") if model_id == DEFAULT_SMALL else "",
BY_ID[model_id].source_path if model_id in BY_ID else "",
row.get("compiler_revision", COMPILER_REVISION), row.get("artifact_format", ARTIFACT_ABI),
True, row["version"], row.get("uses_external_gpu", False))
return tuple(entries.values())
@@ -76,7 +82,8 @@ def model_entries(root: Path | None = None) -> dict[str, ModelEntry]:
"""Resolve validated cached metadata without mutating the bundled catalog."""
from openpilot.starpilot.models.manager import ROOT
root = ROOT if root is None else root
identities = (_document_identity(CATALOG_PATH), _document_identity(root / "catalog.json"))
identities = (_document_identity(CATALOG_PATH), _document_identity(root / "catalog.json"),
_document_identity(NATIVE_CATALOG_PATH), _document_identity(root / "native02e-v1/catalog.json"))
return {entry.model_id: entry for entry in _cached_entries(root, identities)}
@@ -0,0 +1,753 @@
"""Compile StarPilot unified driving artifacts against the vendored tinygrad runtime."""
import argparse
import base64
import hashlib
import json
import math
import os
from pathlib import Path
import pickle
import tempfile
import time
from collections import namedtuple
from functools import partial
import numpy as np
from tinygrad import Device, TinyJit, Context, Tensor
from tinygrad.dtype import _to_np_dtype, dtypes
from tinygrad.device import Buffer, BufferSpec
from tinygrad.uop.ops import UOp
from tinygrad_repo.examples.openpilot.helpers import dump_pickle, load_pickle
COMPILER_REVISION = "02e698160eed1751738212144a7ea2cfb160b3f1"
ARTIFACT_ABI = "tinygrad_single_v2_arena"
_PRE_HCQ_RECORDS = {}
def make_metadata_dict(path, runner):
from tinygrad.nn.onnx import OnnxPBParser
parser = OnnxPBParser(path, load_external_data=False)
properties, output_shapes = {}, {}
for field, wire_type in parser._parse_message(parser.reader.len):
if field == 14:
entry = parser._parse_StringStringEntryProto()
properties[entry['key']] = entry['value']
elif field == 7:
for field, wire_type in parser._parse_message(parser._decode_end_pos()):
if field == 12:
value = parser._parse_ValueInfoProto()
output_shapes[value['name']] = value['parsed_type'].shape if value['parsed_type'] is not None else ()
else:
parser.reader.skip_field(wire_type)
else:
parser.reader.skip_field(wire_type)
if "output_slices" not in properties:
raise ValueError(f"Missing output_slices metadata: {path}")
return {
"model_checkpoint": properties.get("model_checkpoint"),
"output_slices": pickle.loads(base64.b64decode(properties["output_slices"])),
"input_shapes": {name: tuple(spec.shape) for name, spec in runner.graph_inputs.items()},
"output_shapes": output_shapes,
}
ARTIFACT_FORMAT_VERSION = 1
MODEL_TYPES = ("vision_policy", "vision_multi_policy", "supercombo")
NV12Frame = namedtuple("NV12Frame", ["width", "height", "stride", "y_height", "uv_height", "size"])
IMAGE_HISTORY_IN_WARP = "warp"
IMAGE_HISTORY_IN_POLICY = "policy"
IMAGE_HISTORY_PIPELINES = (IMAGE_HISTORY_IN_WARP, IMAGE_HISTORY_IN_POLICY)
LEGACY_WARP_INPUTS = ("img_q", "big_img_q", "tfm", "big_tfm")
FAST_WARP_INPUTS = ("tfm", "big_tfm")
BASE_POLICY_INPUTS = ("feat_q", "desire_q", "packed_npy_inputs")
FAST_POLICY_INPUTS = ("img_q", "big_img_q", *BASE_POLICY_INPUTS)
WARP_INPUTS = LEGACY_WARP_INPUTS
SPLIT_POLICY_INPUTS = BASE_POLICY_INPUTS
SUPERCOMBO_POLICY_INPUTS = BASE_POLICY_INPUTS
WARP_DEV = os.getenv("WARP_DEV")
def _detect_desire_key(input_shapes):
return next((key for key in input_shapes if key.startswith("desire")), None)
def _detect_vision_keys(input_shapes):
image_keys = sorted(key for key in input_shapes if "img" in key)
road_key = next((key for key in image_keys if "big" not in key), None)
wide_key = next((key for key in image_keys if "big" in key), None)
if road_key is None or wide_key is None:
raise ValueError(f"Cannot determine road/wide image keys from {list(input_shapes)}")
return road_key, wide_key
def derive_frame_skip(input_shapes):
features_shape = input_shapes.get("features_buffer")
if features_shape is None:
return 1
return 1 if features_shape[1] >= 99 else 4
def make_random_images(keys, shape, device=None, *, rng=None):
return {key: Tensor.randint(shape, low=0, high=256, dtype="uint8", device=device).realize() for key in keys}
def make_random_blob_images(keys, size, device=None):
keepalive: list[np.ndarray] = []
def make_inputs(*, rng):
nonlocal keepalive
keepalive = []
tensors = {}
for key in keys:
frame = (32 * rng.standard_normal(size).astype(np.float32) + 128).clip(0, 255).astype(np.uint8)
keepalive.append(frame)
tensors[key] = Tensor.from_blob(frame.ctypes.data, (size,), dtype="uint8", device=device).realize()
return tensors
return make_inputs
def warp_perspective_tinygrad(src_flat, matrix_inverse, dst_shape, src_shape, stride_pad, border_fill_val=None):
width_dst, height_dst = dst_shape
height_src, width_src = src_shape
x = Tensor.arange(width_dst).reshape(1, width_dst).expand(height_dst, width_dst).reshape(-1)
y = Tensor.arange(height_dst).reshape(height_dst, 1).expand(height_dst, width_dst).reshape(-1)
src_x = matrix_inverse[0, 0] * x + matrix_inverse[0, 1] * y + matrix_inverse[0, 2]
src_y = matrix_inverse[1, 0] * x + matrix_inverse[1, 1] * y + matrix_inverse[1, 2]
src_w = matrix_inverse[2, 0] * x + matrix_inverse[2, 1] * y + matrix_inverse[2, 2]
src_x = src_x / src_w
src_y = src_y / src_w
x_round = Tensor.round(src_x)
y_round = Tensor.round(src_y)
x_nn_clipped = x_round.clip(0, width_src - 1).cast("int")
y_nn_clipped = y_round.clip(0, height_src - 1).cast("int")
sampled = src_flat[y_nn_clipped * (width_src + stride_pad) + x_nn_clipped]
if border_fill_val is None:
return sampled
in_bounds = ((x_round >= 0) & (x_round <= width_src - 1) &
(y_round >= 0) & (y_round <= height_src - 1)).cast(sampled.dtype)
return sampled * in_bounds + Tensor(border_fill_val, dtype=sampled.dtype) * (1 - in_bounds)
def frames_to_tensor(frames):
height = (frames.shape[0] * 2) // 3
width = frames.shape[1]
return Tensor.cat(
frames[0:height:2, 0::2],
frames[1:height:2, 0::2],
frames[0:height:2, 1::2],
frames[1:height:2, 1::2],
frames[height:height + height // 4].reshape((height // 2, width // 2)),
frames[height + height // 4:height + height // 2].reshape((height // 2, width // 2)),
dim=0,
).reshape((6, height // 2, width // 2))
def make_frame_prepare(nv12: NV12Frame, model_w, model_h):
cam_w, cam_h, stride, y_height, uv_height, _ = nv12
uv_offset = stride * y_height
stride_pad = stride - cam_w
def frame_prepare(input_frame, matrix_inverse):
matrix_inverse_uv = matrix_inverse * Tensor(
[[1.0, 1.0, 0.5], [1.0, 1.0, 0.5], [2.0, 2.0, 1.0]],
device=WARP_DEV,
)
uv = input_frame[uv_offset:uv_offset + uv_height * stride].reshape(uv_height, stride)
with Context(SPLIT_REDUCEOP=0):
y = warp_perspective_tinygrad(
input_frame[:cam_h * stride], matrix_inverse, (model_w, model_h), (cam_h, cam_w), stride_pad,
).realize()
u = warp_perspective_tinygrad(
uv[:cam_h // 2, :cam_w:2].flatten(), matrix_inverse_uv,
(model_w // 2, model_h // 2), (cam_h // 2, cam_w // 2), 0,
).realize()
v = warp_perspective_tinygrad(
uv[:cam_h // 2, 1:cam_w:2].flatten(), matrix_inverse_uv,
(model_w // 2, model_h // 2), (cam_h // 2, cam_w // 2), 0,
).realize()
return frames_to_tensor(y.cat(u).cat(v).reshape((model_h * 3 // 2, model_w)))
return frame_prepare
def make_warp_input_queues(vision_input_shapes, frame_skip, device):
road_key, _ = _detect_vision_keys(vision_input_shapes)
image_shape = vision_input_shapes[road_key]
frame_count = image_shape[1] // 6
image_buffer_shape = (frame_skip * (frame_count - 1) + 1, 6, image_shape[2], image_shape[3])
npy = {
"tfm": np.zeros((3, 3), dtype=np.float32),
"big_tfm": np.zeros((3, 3), dtype=np.float32),
}
queues = {
"img_q": Tensor(np.zeros(image_buffer_shape, dtype=np.uint8), device=device).contiguous().realize(),
"big_img_q": Tensor(np.zeros(image_buffer_shape, dtype=np.uint8), device=device).contiguous().realize(),
**{key: Tensor(value, device="NPY").realize() for key, value in npy.items()},
}
return queues, npy
def _packed_policy_shapes(input_shapes, include_prev_feature=False):
desire_key = _detect_desire_key(input_shapes)
if desire_key is None:
raise ValueError(f"No desire input found in {list(input_shapes)}")
shapes = {"desire": (input_shapes[desire_key][2],)}
for key, shape in input_shapes.items():
if key in ("features_buffer", desire_key) or "img" in key:
continue
shapes[key] = tuple(shape)
if include_prev_feature:
features_shape = input_shapes["features_buffer"]
shapes["prev_feat"] = (features_shape[0], math.prod(features_shape[2:]))
return shapes, [math.prod(shape) for shape in shapes.values()]
def make_split_input_queues(vision_input_shapes, policy_input_shapes, frame_skip, device):
queues, npy = make_warp_input_queues(vision_input_shapes, frame_skip, device)
features_shape = policy_input_shapes["features_buffer"]
feature_dim = math.prod(features_shape[2:])
desire_key = _detect_desire_key(policy_input_shapes)
desire_shape = policy_input_shapes[desire_key]
packed_shapes, packed_sizes = _packed_policy_shapes(policy_input_shapes)
packed_inputs = np.zeros(sum(packed_sizes), dtype=np.float32)
npy.update({
key: value.reshape(shape)
for (key, shape), value in zip(
packed_shapes.items(), np.split(packed_inputs, np.cumsum(packed_sizes[:-1])), strict=True,
)
})
queues.update({
"feat_q": Tensor(
np.zeros((frame_skip * (features_shape[1] - 1) + 1, features_shape[0], feature_dim), dtype=np.float32),
device=device,
).contiguous().realize(),
"desire_q": Tensor(
np.zeros((frame_skip * desire_shape[1], desire_shape[0], desire_shape[2]), dtype=np.float32),
device=device,
).contiguous().realize(),
"packed_npy_inputs": Tensor(packed_inputs, device="NPY").realize(),
})
return queues, npy
def make_supercombo_input_queues(input_shapes, frame_skip, device):
queues, npy = make_warp_input_queues(input_shapes, frame_skip, device)
features_shape = input_shapes["features_buffer"]
feature_dim = math.prod(features_shape[2:])
desire_key = _detect_desire_key(input_shapes)
desire_shape = input_shapes[desire_key]
packed_shapes, packed_sizes = _packed_policy_shapes(input_shapes, include_prev_feature=True)
packed_inputs = np.zeros(sum(packed_sizes), dtype=np.float32)
npy.update({
key: value.reshape(shape)
for (key, shape), value in zip(
packed_shapes.items(), np.split(packed_inputs, np.cumsum(packed_sizes[:-1])), strict=True,
)
})
queues.update({
"feat_q": Tensor(
np.zeros((frame_skip * features_shape[1], features_shape[0], feature_dim), dtype=np.float32),
device=device,
).contiguous().realize(),
"desire_q": Tensor(
np.zeros((frame_skip * desire_shape[1], desire_shape[0], desire_shape[2]), dtype=np.float32),
device=device,
).contiguous().realize(),
"packed_npy_inputs": Tensor(packed_inputs, device="NPY").realize(),
})
return queues, npy
def shift_and_sample(buffer, new_value, sample_fn):
buffer.assign(buffer[1:].cat(new_value, dim=0).contiguous())
return sample_fn(buffer)
def sample_skip(buffer, frame_skip):
return buffer[::frame_skip].contiguous().flatten(0, 1).unsqueeze(0)
def sample_desire(buffer, frame_skip):
return buffer.reshape(-1, frame_skip, *buffer.shape[1:]).max(1).flatten(0, 1).unsqueeze(0)
def make_warp(nv12, model_w, model_h, frame_skip, image_history_pipeline=IMAGE_HISTORY_IN_POLICY):
frame_prepare = make_frame_prepare(nv12, model_w, model_h)
if image_history_pipeline == IMAGE_HISTORY_IN_POLICY:
def warp(tfm, big_tfm, frame, big_frame):
tfm = tfm.to(WARP_DEV)
big_tfm = big_tfm.to(WARP_DEV)
Tensor.realize(tfm, big_tfm)
return Tensor.cat(
frame_prepare(frame, tfm).unsqueeze(0),
frame_prepare(big_frame, big_tfm).unsqueeze(0),
)
return warp
sample_skip_fn = partial(sample_skip, frame_skip=frame_skip)
def warp_enqueue(img_q, big_img_q, tfm, big_tfm, frame, big_frame):
tfm = tfm.to(WARP_DEV)
big_tfm = big_tfm.to(WARP_DEV)
Tensor.realize(tfm, big_tfm)
warped = Tensor.cat(
frame_prepare(frame, tfm).unsqueeze(0),
frame_prepare(big_frame, big_tfm).unsqueeze(0),
).to(Device.DEFAULT)
img = shift_and_sample(img_q, warped[0:1], sample_skip_fn)
big_img = shift_and_sample(big_img_q, warped[1:2], sample_skip_fn)
return img, big_img
return warp_enqueue
def make_run_split_policy(vision_runner, policy_runners, metadata, policy_order, frame_skip,
image_history_pipeline=IMAGE_HISTORY_IN_POLICY):
sample_desire_fn = partial(sample_desire, frame_skip=frame_skip)
sample_skip_fn = partial(sample_skip, frame_skip=frame_skip)
vision_metadata = metadata["vision"]
policy_metadata = metadata[policy_order[0]]
vision_features_slice = vision_metadata["output_slices"]["hidden_state"]
desire_key = _detect_desire_key(policy_metadata["input_shapes"])
packed_shapes, packed_sizes = _packed_policy_shapes(policy_metadata["input_shapes"])
road_key, wide_key = _detect_vision_keys(vision_metadata["input_shapes"])
def run_model(img, big_img, feat_q, desire_q, packed_npy_inputs):
unpacked = {
key: tensor.reshape(shape)
for (key, shape), tensor in zip(
packed_shapes.items(), packed_npy_inputs.split(packed_sizes), strict=True,
)
}
desire_buffer = shift_and_sample(
desire_q, unpacked.pop("desire").reshape(1, 1, -1), sample_desire_fn,
)
vision_output = next(iter(vision_runner({road_key: img, wide_key: big_img}).values())).cast("float32")
new_feature = vision_output[:, vision_features_slice].reshape(1, -1).unsqueeze(0)
features_buffer = shift_and_sample(feat_q, new_feature, sample_skip_fn)
features_buffer = features_buffer.reshape(policy_metadata["input_shapes"]["features_buffer"])
policy_inputs = {
"features_buffer": features_buffer,
desire_key: desire_buffer,
**unpacked,
}
policy_outputs = [
next(iter(policy_runners[key](policy_inputs).values())).cast("float32")
for key in policy_order
]
return (vision_output, *policy_outputs)
if image_history_pipeline == IMAGE_HISTORY_IN_POLICY:
def run_policy(warped, img_q, big_img_q, feat_q, desire_q, packed_npy_inputs):
packed_npy_inputs = packed_npy_inputs.to(Device.DEFAULT)
warped = warped.to(Device.DEFAULT)
Tensor.realize(packed_npy_inputs, warped)
img = shift_and_sample(img_q, warped[0:1], sample_skip_fn)
big_img = shift_and_sample(big_img_q, warped[1:2], sample_skip_fn)
return run_model(img, big_img, feat_q, desire_q, packed_npy_inputs)
return run_policy
def run_policy(img, big_img, feat_q, desire_q, packed_npy_inputs):
packed_npy_inputs = packed_npy_inputs.to(Device.DEFAULT).realize()
return run_model(img, big_img, feat_q, desire_q, packed_npy_inputs)
return run_policy
def make_run_supercombo(model_runner, metadata, frame_skip, image_history_pipeline=IMAGE_HISTORY_IN_POLICY):
input_shapes = metadata["model"]["input_shapes"]
sample_desire_fn = partial(sample_desire, frame_skip=frame_skip)
sample_skip_fn = partial(sample_skip, frame_skip=frame_skip)
desire_key = _detect_desire_key(input_shapes)
packed_shapes, packed_sizes = _packed_policy_shapes(input_shapes, include_prev_feature=True)
road_key, wide_key = _detect_vision_keys(input_shapes)
def run_model(img, big_img, feat_q, desire_q, packed_npy_inputs):
unpacked = {
key: tensor.reshape(shape)
for (key, shape), tensor in zip(
packed_shapes.items(), packed_npy_inputs.split(packed_sizes), strict=True,
)
}
desire_buffer = shift_and_sample(
desire_q, unpacked.pop("desire").reshape(1, 1, -1), sample_desire_fn,
)
previous_feature = unpacked.pop("prev_feat")
features_buffer = shift_and_sample(
feat_q, previous_feature.reshape(1, 1, -1), sample_skip_fn,
)
features_buffer = features_buffer.reshape(input_shapes["features_buffer"])
model_inputs = {
road_key: img,
wide_key: big_img,
"features_buffer": features_buffer,
desire_key: desire_buffer,
**unpacked,
}
model_output = next(iter(model_runner(model_inputs).values())).cast("float32")
return model_output,
if image_history_pipeline == IMAGE_HISTORY_IN_POLICY:
def run_policy(warped, img_q, big_img_q, feat_q, desire_q, packed_npy_inputs):
packed_npy_inputs = packed_npy_inputs.to(Device.DEFAULT)
if (isinstance(warped.device, str) and warped.device.split(":")[0] == "QCOM" and
Device.DEFAULT.split(":")[0] == "AMD" and getattr(Device.default, "is_usb", False)):
warped = warped.to("CPU").realize()
warped = warped.to(Device.DEFAULT)
Tensor.realize(packed_npy_inputs, warped)
img = shift_and_sample(img_q, warped[0:1], sample_skip_fn)
big_img = shift_and_sample(big_img_q, warped[1:2], sample_skip_fn)
return run_model(img, big_img, feat_q, desire_q, packed_npy_inputs)
return run_policy
def run_policy(img, big_img, feat_q, desire_q, packed_npy_inputs):
packed_npy_inputs = packed_npy_inputs.to(Device.DEFAULT).realize()
return run_model(img, big_img, feat_q, desire_q, packed_npy_inputs)
return run_policy
def stateful_image_shapes(metadata):
shape = tuple(metadata['input_shapes']['new_img'])
if len(shape) != 4 or shape[:2] != (2, 6):
raise ValueError(f"Unsupported stateful image shape: {shape}")
return dict.fromkeys(('img', 'big_img'), (1, *shape[1:]))
def stateful_host_shapes(metadata):
return {name: shape for name, shape in metadata['input_shapes'].items()
if name != 'new_img' and name not in metadata['state_pairs']}
def make_stateful_input_queues(metadata, device):
queues, npy = make_warp_input_queues(stateful_image_shapes(metadata), 1, device)
shapes = stateful_host_shapes(metadata)
sizes = [math.prod(shape) for shape in shapes.values()]
packed = np.zeros(sum(sizes), dtype=np.float32)
npy.update({name: value.reshape(shape) for (name, shape), value in
zip(shapes.items(), np.split(packed, np.cumsum(sizes[:-1])), strict=True)})
queues['packed_npy_inputs'] = Tensor(packed, device='NPY').realize()
if metadata.get('mapped_image_input', False):
image_shape = tuple(metadata['input_shapes']['new_img'])
buffer = Buffer(device, math.prod(image_shape) * dtypes.uint8.itemsize,
options=BufferSpec(cpu_access=True), preallocate=True)
view = UOp.from_buffer(buffer, dtypes.uint8)
queues['mapped_gpu_image'] = Tensor(view).reshape(image_shape)
for name in metadata['state_pairs']:
queues[name] = Tensor(np.zeros(metadata['input_shapes'][name], dtype=metadata['input_dtypes'][name]),
device=device).contiguous().realize()
return queues, npy
def make_run_stateful_supercombo(model_runner, metadata):
shapes = stateful_host_shapes(metadata)
sizes = [math.prod(shape) for shape in shapes.values()]
def run_policy(warped, packed_npy_inputs, mapped_gpu_image=None, **state):
packed = packed_npy_inputs.to(Device.DEFAULT).realize()
inputs = {name: value.reshape(shape).cast(model_runner.graph_inputs[name].dtype)
for (name, shape), value in zip(shapes.items(), packed.split(sizes), strict=True)}
if metadata.get('mapped_image_input', False):
if mapped_gpu_image is None:
raise ValueError('Mapped stateful image queue is missing')
image = warped.to('CPU').realize().to(Device.DEFAULT)
image = mapped_gpu_image.assign(image).realize()
else:
image = warped.to(Device.DEFAULT)
inputs['new_img'] = image.cast(model_runner.graph_inputs['new_img'].dtype)
outputs = {name: value.contiguous() for name, value in model_runner(inputs | state).items()}
for name, next_name in metadata['state_pairs'].items():
if outputs[next_name].dtype != state[name].dtype:
raise ValueError(f'State dtype mismatch: {name} -> {next_name}')
Tensor.realize(*outputs.values())
Tensor.realize(*(state[name].assign(outputs[next_name]) for name, next_name in metadata['state_pairs'].items()))
return outputs['outputs'].cast('float32'),
return run_policy
def compile_jit(jit, make_random_inputs, input_keys, make_queues, validation_runs=1):
seed = 42
def random_inputs_run(fn, current_seed, test_values=None, test_buffers=None, expect_match=True):
input_queues, npy = make_queues(Device.DEFAULT)
rng = np.random.default_rng(current_seed)
Tensor.manual_seed(current_seed)
testing = test_values is not None or test_buffers is not None
run_count = validation_runs if testing else max(3, validation_runs)
values, buffers = [], []
for index in range(run_count):
for value in npy.values():
value[:] = rng.standard_normal(value.shape).astype(value.dtype)
Device.default.synchronize()
random_inputs = make_random_inputs(rng=rng)
start = time.perf_counter()
outputs = fn(**{key: input_queues[key] for key in input_keys}, **random_inputs)
mid = time.perf_counter()
Device.default.synchronize()
end = time.perf_counter()
print(f" [{index + 1}/{run_count}] enqueue {(mid - start) * 1e3:6.2f} ms -- total {(end - start) * 1e3:6.2f} ms")
if index < validation_runs:
values.extend(np.copy(value.numpy()) for value in outputs)
buffers.extend(np.copy(value.numpy()) for value in input_queues.values())
if not all(np.isfinite(value).all() for value in values):
raise ValueError("Compiled JIT produced non-finite outputs")
if test_values is not None:
match = all(np.array_equal(lhs, rhs) for lhs, rhs in zip(values, test_values, strict=True))
assert match == expect_match, f"outputs {'differ from' if expect_match else 'match'} baseline (seed={current_seed})"
if test_buffers is not None:
match = all(np.array_equal(lhs, rhs) for lhs, rhs in zip(buffers, test_buffers, strict=True))
assert match == expect_match, f"buffers {'differ from' if expect_match else 'match'} baseline (seed={current_seed})"
return values, buffers
from tinygrad.engine import realize
from unittest.mock import patch
original_hcq_compile = realize.hcq_compile
schedules = []
def retain_schedule(linear, input_uops, profile, cache=False):
compiled = original_hcq_compile(linear, input_uops, profile, cache=cache)
schedules.append((linear, compiled))
return compiled
print("capture + replay")
with patch.object(realize, 'hcq_compile', retain_schedule):
test_values, test_buffers = random_inputs_run(jit, seed)
captured = jit.captured
matching = [linear for linear, compiled in schedules if compiled is captured._linear]
if len(matching) != 1:
raise ValueError("Missing or ambiguous pre-HCQ schedule for captured JIT")
_PRE_HCQ_RECORDS[id(jit)] = {'linear': matching[0], 'ret': captured.ret,
'expected_names': captured.expected_names,
'expected_input_info': captured.expected_input_info}
print("pickle round trip")
with tempfile.TemporaryDirectory(prefix="starpilot-jit-") as temporary:
path = Path(temporary) / "captured.pkl"
dump_pickle(jit, path)
loaded = load_pickle(path, out_of_band=True)
random_inputs_run(loaded, seed, test_values, test_buffers, expect_match=True)
random_inputs_run(loaded, seed + 1, test_values, test_buffers, expect_match=False)
return jit
def _parse_size(value):
width, height = value.lower().split("x")
return int(width), int(height)
def validate_metadata(metadata):
output_shapes = metadata.get("output_shapes", {})
output_shape = output_shapes.get("outputs")
if not output_shape or len(output_shape) < 2:
raise ValueError(f"Invalid model output shape metadata: {output_shapes}")
output_size = output_shape[-1]
for name, output_slice in metadata.get("output_slices", {}).items():
start, stop, step = output_slice.indices(output_size)
if step != 1 or start < 0 or stop < start or stop > output_size:
raise ValueError(f"Invalid output slice {name}={output_slice} for output size {output_size}")
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--model-type", choices=MODEL_TYPES, required=True)
parser.add_argument("--model-size", type=_parse_size, required=True)
parser.add_argument("--camera-resolutions", type=_parse_size, nargs="+", required=True)
parser.add_argument("--frame-skip", type=int)
parser.add_argument("--behavior-version")
parser.add_argument("--output", required=True)
parser.add_argument("--out-of-band", action="store_true", help="Compatibility flag; all artifacts use the current buffer arena format.")
parser.add_argument("--vision-onnx")
parser.add_argument("--policy-onnx")
parser.add_argument("--off-policy-onnx")
parser.add_argument("--on-policy-onnx")
parser.add_argument("--supercombo-onnx")
parser.add_argument(
"--image-history-pipeline",
choices=IMAGE_HISTORY_PIPELINES,
default=IMAGE_HISTORY_IN_POLICY,
help="Where img/big_img history queues are updated. 'policy' is the newer faster ABI; 'warp' reproduces legacy v22 artifacts.",
)
args = parser.parse_args()
if args.frame_skip is not None and args.frame_skip < 1:
parser.error("--frame-skip must be positive")
from tinygrad.nn.onnx import OnnxRunner
from openpilot.system.camerad.cameras.nv12_info import get_nv12_info
output = {
"format_version": ARTIFACT_FORMAT_VERSION,
"artifact_abi": ARTIFACT_ABI,
"compiler_revision": COMPILER_REVISION,
"execution_device": Device.DEFAULT,
"warp_device": WARP_DEV or Device.DEFAULT,
"model_type": args.model_type,
"metadata": {},
"image_history_pipeline": args.image_history_pipeline,
}
if args.behavior_version:
output["behavior_version"] = args.behavior_version
if args.model_type == "supercombo":
if not args.supercombo_onnx:
parser.error("--supercombo-onnx is required for supercombo")
model_path = Path(args.supercombo_onnx)
model_runner = OnnxRunner(model_path)
output["metadata"]["model"] = make_metadata_dict(model_path, model_runner)
validate_metadata(output["metadata"]["model"])
policy_shapes = output["metadata"]["model"]["input_shapes"]
if 'new_img' in policy_shapes:
if args.image_history_pipeline != IMAGE_HISTORY_IN_POLICY:
parser.error('ONNX-managed history requires --image-history-pipeline policy')
metadata = output['metadata']['model']
metadata['state_pairs'] = {name: f'next_{name}' for name in policy_shapes
if f'next_{name}' in metadata['output_shapes']}
if not metadata['state_pairs']:
raise ValueError('Stateful supercombo is missing next-state outputs')
metadata['input_dtypes'] = {name: np.dtype(_to_np_dtype(spec.dtype)).name for name, spec in model_runner.graph_inputs.items()}
for name, next_name in metadata['state_pairs'].items():
if policy_shapes[name] != metadata['output_shapes'][next_name]:
raise ValueError(f'State shape mismatch: {name} -> {next_name}')
metadata['mapped_image_input'] = True
frame_skip = 1
make_policy_queues = partial(make_stateful_input_queues, metadata)
run_policy = make_run_stateful_supercombo(model_runner, metadata)
image_shapes = stateful_image_shapes(metadata)
policy_input_keys = ('packed_npy_inputs', 'mapped_gpu_image', *metadata['state_pairs'])
else:
frame_skip = args.frame_skip or derive_frame_skip(policy_shapes)
make_policy_queues = partial(make_supercombo_input_queues, policy_shapes, frame_skip)
run_policy = make_run_supercombo(
model_runner, output["metadata"], frame_skip, args.image_history_pipeline,
)
image_shapes = policy_shapes
policy_input_keys = FAST_POLICY_INPUTS if args.image_history_pipeline == IMAGE_HISTORY_IN_POLICY else SUPERCOMBO_POLICY_INPUTS
else:
if not args.vision_onnx:
parser.error("--vision-onnx is required for split models")
policy_paths = {}
if args.policy_onnx:
policy_paths["policy"] = args.policy_onnx
if args.off_policy_onnx:
policy_paths["off_policy"] = args.off_policy_onnx
if args.on_policy_onnx:
policy_paths["on_policy"] = args.on_policy_onnx
if args.model_type == "vision_policy" and set(policy_paths) != {"policy"}:
parser.error("vision_policy requires --policy-onnx")
if args.model_type == "vision_multi_policy" and not policy_paths:
parser.error("vision_multi_policy requires at least one policy ONNX")
vision_path = Path(args.vision_onnx)
resolved_policy_paths = {key: Path(path) for key, path in policy_paths.items()}
vision_runner = OnnxRunner(vision_path)
policy_runners = {key: OnnxRunner(path) for key, path in resolved_policy_paths.items()}
output["metadata"]["vision"] = make_metadata_dict(vision_path, vision_runner)
validate_metadata(output["metadata"]["vision"])
for key, path in resolved_policy_paths.items():
output["metadata"][key] = make_metadata_dict(path, policy_runners[key])
validate_metadata(output["metadata"][key])
policy_order = [key for key in ("on_policy", "off_policy", "policy") if key in policy_runners]
output["policy_order"] = policy_order
first_policy_shapes = output["metadata"][policy_order[0]]["input_shapes"]
for key in policy_order[1:]:
if output["metadata"][key]["input_shapes"] != first_policy_shapes:
raise ValueError(f"Policy input shapes differ between {policy_order[0]} and {key}")
frame_skip = args.frame_skip or derive_frame_skip(first_policy_shapes)
make_policy_queues = partial(
make_split_input_queues,
output["metadata"]["vision"]["input_shapes"],
first_policy_shapes,
frame_skip,
)
run_policy = make_run_split_policy(
vision_runner, policy_runners, output["metadata"], policy_order, frame_skip,
args.image_history_pipeline,
)
image_shapes = output["metadata"]["vision"]["input_shapes"]
policy_input_keys = FAST_POLICY_INPUTS if args.image_history_pipeline == IMAGE_HISTORY_IN_POLICY else SPLIT_POLICY_INPUTS
output["frame_skip"] = frame_skip
output["policy_input_keys"] = policy_input_keys
warp_input_keys = FAST_WARP_INPUTS if args.image_history_pipeline == IMAGE_HISTORY_IN_POLICY else LEGACY_WARP_INPUTS
output["warp_input_keys"] = warp_input_keys
run_policy_jit = TinyJit(run_policy, prune=True)
road_key, wide_key = _detect_vision_keys(image_shapes)
if args.image_history_pipeline == IMAGE_HISTORY_IN_POLICY:
make_random_model_inputs = partial(
make_random_images,
keys=["warped"],
shape=(2, 6, *image_shapes[road_key][2:]),
device=WARP_DEV,
)
else:
make_random_model_inputs = partial(
make_random_images,
keys=[road_key, wide_key],
shape=image_shapes[road_key],
)
output["run_policy"] = compile_jit(
run_policy_jit, make_random_model_inputs, policy_input_keys, make_policy_queues,
validation_runs=5 if output['metadata'].get('model', {}).get('state_pairs') else 1,
)
model_w, model_h = args.model_size
for cam_w, cam_h in args.camera_resolutions:
nv12 = NV12Frame(cam_w, cam_h, *get_nv12_info(cam_w, cam_h))
warp_enqueue = TinyJit(
make_warp(nv12, model_w, model_h, frame_skip, args.image_history_pipeline),
prune=True,
)
make_random_warp_inputs = make_random_blob_images(
keys=["frame", "big_frame"], size=nv12.size, device=WARP_DEV,
)
make_warp_queues = partial(make_warp_input_queues, image_shapes, frame_skip)
output[(cam_w, cam_h)] = compile_jit(
warp_enqueue, make_random_warp_inputs, warp_input_keys, make_warp_queues,
)
dump_pickle(output, args.output)
with open(args.output, 'rb') as native_file:
native_sha256 = hashlib.file_digest(native_file, 'sha256').hexdigest()
launch_path = Path(args.output).with_suffix('.launch.pkl')
launch = {
'format': 'tinygrad_pre_hcq_v1', 'compiler_revision': COMPILER_REVISION,
'artifact_abi': ARTIFACT_ABI, 'native_artifact_sha256': native_sha256,
'native_artifact_name': Path(args.output).name,
'captures': {key: _PRE_HCQ_RECORDS.pop(id(value)) for key, value in output.items()
if key == 'run_policy' or isinstance(key, tuple)},
}
dump_pickle(launch, launch_path)
with launch_path.open('rb') as launch_file:
launch_sha256 = hashlib.file_digest(launch_file, 'sha256').hexdigest()
launch_path.with_suffix('.json').write_text(json.dumps({
'format': launch['format'], 'compiler_revision': COMPILER_REVISION, 'artifact_abi': ARTIFACT_ABI,
'native_artifact_sha256': native_sha256, 'launch_sha256': launch_sha256,
}, indent=2) + '\n')
print(f"Saved JITs to {args.output} ({os.path.getsize(args.output) / 1e6:.2f} MB)")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+144 -36
View File
@@ -22,7 +22,11 @@ from urllib.request import urlopen
from openpilot.starpilot.models.catalog import (
ARTIFACT_ABI, BUNDLED_CURRENT, BY_ID, CATALOG_PATH, COMPILER_REVISION, DEFAULT_SMALL, DEFAULT_SMALL_SHA256,
DEFAULT_SMALL_SIZE, GENERATION, model_entries,
DEFAULT_SMALL_SIZE, GENERATION, NATIVE_CATALOG_PATH, model_entries,
)
from openpilot.starpilot.models.runtime_selection import (
NATIVE_ABI, NATIVE_GENERATION, NATIVE_REVISION, RuntimePlan, freeze_plan, selection_family,
)
RESOURCE_URL = "https://huggingface.co/buckets/StarPilot-Driving/StarPilot-Resources/resolve"
@@ -74,9 +78,12 @@ def state_lock(root: Path):
yield
def validate_manifest(data: object) -> dict[str, dict]:
if (not isinstance(data, dict) or data.get("generation") != GENERATION or data.get("compiler_revision") != COMPILER_REVISION or
data.get("artifact_abi") != ARTIFACT_ABI or not isinstance(data.get("models"), list)):
def validate_manifest(data: object, *, native: bool = False) -> dict[str, dict]:
generation = NATIVE_GENERATION if native else GENERATION
revision = NATIVE_REVISION if native else COMPILER_REVISION
abi = NATIVE_ABI if native else ARTIFACT_ABI
if (not isinstance(data, dict) or data.get("generation") != generation or data.get("compiler_revision") != revision or
data.get("artifact_abi") != abi or not isinstance(data.get("models"), list)):
raise ModelError("The model catalog targets a different runtime")
rows = {}
for entry in data["models"]:
@@ -107,7 +114,7 @@ def validate_manifest(data: object) -> dict[str, dict]:
amd = variants.get("amd")
if amd is not None and (not isinstance(amd, dict) or entry.get("uses_external_gpu", False) or
entry.get("model_lab_eligible") is not True or amd.get("execution_device") != "AMD" or
amd.get("compiler_revision") != COMPILER_REVISION):
amd.get("compiler_revision") != revision):
raise ModelError("Invalid laboratory artifact hardware or runtime")
runtime_artifacts = entry.get("runtime_artifacts", [])
if not isinstance(runtime_artifacts, list):
@@ -127,7 +134,7 @@ def validate_manifest(data: object) -> dict[str, dict]:
size, chunks = artifact.get("artifact_size"), artifact.get("artifact_chunk_count")
if (not isinstance(artifact["artifact_sha256"], str) or not SHA.fullmatch(artifact["artifact_sha256"]) or
type(size) is not int or not 0 < size <= MAX_ARTIFACT or type(chunks) is not int or not 0 <= chunks <= 99 or
artifact.get("artifact_format") != ARTIFACT_ABI):
artifact.get("artifact_format") != abi):
raise ModelError("Invalid compiled artifact metadata")
row = dict(entry)
compatible = [artifact for artifact in runtime_artifacts if artifact["min_runner_revision"] <= MODEL_RUNNER_REVISION]
@@ -136,19 +143,97 @@ def validate_manifest(data: object) -> dict[str, dict]:
row.update({key: value for key, value in selected.items() if key != "min_runner_revision"})
if known is None and "artifact_sha256" not in row:
raise ModelError("New models require a compatible compiled artifact")
if native and (model_id != DEFAULT_SMALL and row.get("uses_external_gpu") is not True or
"artifact_sha256" not in row or not isinstance(row.get("qualification_sha256"), str) or
not SHA.fullmatch(row["qualification_sha256"]) or variants or runtime_artifacts):
raise ModelError("Native rows require qualified RDF or Chestnut artifacts")
row.pop("_artifact_path", None)
row.update(compiler_revision=revision, _runtime_family="native02e" if native else "legacy9d",
_generation=generation)
rows[model_id] = row
return rows
def native_catalog(root: Path = ROOT) -> dict[str, dict]:
rows = {}
for path in (NATIVE_CATALOG_PATH, root / NATIVE_GENERATION / "catalog.json"):
try:
rows.update(validate_manifest(read_json(path), native=True))
except FileNotFoundError:
continue
return rows
def native_stock(root: Path = ROOT) -> dict[str, dict]:
# Stock DM is release-pinned, never supplied by the downloadable model catalog.
document = read_json(NATIVE_CATALOG_PATH)
validate_manifest(document, native=True)
stock = document.get("stock")
required = {"dmonitoring_model_tinygrad.pkl", "dm_warp_1928x1208_tinygrad.pkl", "dm_warp_1344x760_tinygrad.pkl"}
if not isinstance(stock, dict) or set(stock) != required:
raise ModelError("Native monitoring release is not qualified")
for row in stock.values():
if (not isinstance(row, dict) or not isinstance(row.get("artifact_sha256"), str) or
not SHA.fullmatch(row["artifact_sha256"]) or type(row.get("artifact_size")) is not int or
not 0 < row["artifact_size"] <= MAX_ARTIFACT or not isinstance(row.get("qualification_sha256"), str) or
not SHA.fullmatch(row["qualification_sha256"])):
raise ModelError("Invalid native monitoring artifact identity")
return stock
def catalog(root: Path = ROOT) -> dict[str, dict]:
base = validate_manifest(read_json(CATALOG_PATH))
try:
base.update(validate_manifest(read_json(root / "catalog.json")))
except (OSError, ValueError, TypeError):
pass
# Qualified native entries replace only matching IDs; all other v26 payloads remain usable.
try:
base.update(native_catalog(root))
except (OSError, ValueError, TypeError):
pass
return base
def default_record(family: str, root: Path = ROOT) -> dict:
if family == "legacy9d":
return {"path": str(SHIPPED_MODELS / "compat9d" / f"{DEFAULT_SMALL}_driving_tinygrad.pkl"),
"artifact_sha256": DEFAULT_SMALL_SHA256, "artifact_size": DEFAULT_SMALL_SIZE,
"version": BY_ID[DEFAULT_SMALL].version, "runtime_family": family}
if family != "native02e":
raise ModelError("Unknown default runtime")
row = native_catalog(root).get(DEFAULT_SMALL)
if row is None:
raise ModelError("Native RDF default is not qualified")
native_stock(root)
return {"path": str(SHIPPED_MODELS / f"{DEFAULT_SMALL}_driving_tinygrad.pkl"),
"artifact_sha256": row["artifact_sha256"], "artifact_size": row["artifact_size"],
"version": row["version"], "runtime_family": family}
def freeze_runtime(*, root: Path = ROOT, recovery_small_only: bool = False,
chestnut_available: bool | None = None, randomize: bool = True) -> RuntimePlan:
# All called modules are stdlib-only until prepare() chooses the family.
if chestnut_available is None:
from openpilot.selfdrive.modeld.helpers import chestnut_present
from openpilot.common.hardware.usb import cable_connected
from openpilot.starpilot.models.jetlink_adapter import link_on
chestnut_available = chestnut_present() or (cable_connected() and not link_on())
if randomize and not recovery_small_only:
randomize_next_start(chestnut_available, root=root)
with state_lock(root):
prefs, entries = preferences(root), catalog(root)
family = selection_family(prefs, entries, recovery_small_only=recovery_small_only)
for mid, row in entries.items():
directory = root / NATIVE_GENERATION if row.get("_runtime_family") == "native02e" else root
row["_artifact_path"] = str(directory / mid / f"{mid}_driving_tinygrad.pkl")
fallback = default_record(family, root)
plan = freeze_plan(prefs, entries, fallback, recovery_small_only=recovery_small_only)
if shipped_default(plan) is None:
raise ModelError("Selected runtime RDF fallback is missing or corrupt")
return plan
def preferences(root: Path = ROOT) -> dict:
default = {"small": DEFAULT_SMALL, "big": "", "userFavorites": [], "sortMode": "name",
"randomizer": False, "blacklistedModels": []}
@@ -172,14 +257,16 @@ def preferences(root: Path = ROOT) -> dict:
return default
def artifact_path(model_id: str, root: Path = ROOT, variant: str = "standard") -> Path:
def artifact_path(model_id: str, root: Path = ROOT, variant: str = "standard", *, family: str | None = None) -> Path:
entries = model_entries(root)
if not isinstance(model_id, str) or model_id not in entries or model_id == BUNDLED_CURRENT:
raise ModelError("Unknown downloadable model")
if variant not in ("standard", "amd") or (variant == "amd" and entries[model_id].uses_external_gpu):
raise ModelError("Unknown model artifact variant")
suffix = "amd_" if variant == "amd" else ""
return root / model_id / f"{model_id}_driving_{suffix}tinygrad.pkl"
family = catalog(root).get(model_id, {}).get("_runtime_family") if family is None else family
directory = root / NATIVE_GENERATION if family == "native02e" else root
return directory / model_id / f"{model_id}_driving_{suffix}tinygrad.pkl"
def artifact_entry(model_id: str, entries: dict[str, dict], variant: str = "standard") -> dict:
@@ -195,7 +282,8 @@ def verified_artifact(model_id: str, entries: dict[str, dict], root: Path = ROOT
row = artifact_entry(model_id, entries, variant)
if "artifact_sha256" not in row:
return None
path = artifact_path(model_id, root, variant)
path = Path(row["_artifact_path"]) if variant == "standard" and "_artifact_path" in row else artifact_path(
model_id, root, variant, family=row.get("_runtime_family"))
try:
if path.is_symlink() or not path.is_file() or path.stat().st_size != row["artifact_size"]:
return None
@@ -206,13 +294,13 @@ def verified_artifact(model_id: str, entries: dict[str, dict], root: Path = ROOT
return None
def shipped_default() -> Path | None:
def shipped_default(plan: RuntimePlan | None = None) -> Path | None:
from openpilot.common.file_chunker import materialize_file_chunked
try:
path = materialize_file_chunked(SHIPPED_MODELS / f"{DEFAULT_SMALL}_driving_tinygrad.pkl",
expected_sha256=DEFAULT_SMALL_SHA256)
return path if path.stat().st_size == DEFAULT_SMALL_SIZE else None
row = json.loads(plan.default_json) if plan is not None else default_record("native02e")
path = materialize_file_chunked(Path(row["path"]), expected_sha256=row["artifact_sha256"])
return path if path.stat().st_size == row["artifact_size"] else None
except (OSError, ValueError, TypeError, KeyError):
return None
@@ -228,10 +316,13 @@ class RuntimeSelection:
allow_big: bool = True
small_sha256: str | None = None
big_sha256: str | None = None
fallback_path: Path | None = None
fallback_version: str = "current"
fallback_sha256: str | None = None
def requested_runtime_id(chestnut_available: bool, *, root: Path = ROOT) -> str:
prefs = preferences(root)
def requested_runtime_id(chestnut_available: bool, *, root: Path = ROOT, plan: RuntimePlan | None = None) -> str:
prefs = json.loads(plan.preferences_json) if plan is not None else preferences(root)
return prefs["big"] if chestnut_available and prefs["big"] else prefs["small"]
@@ -253,26 +344,36 @@ def randomize_next_start(chestnut_available: bool, *, root: Path = ROOT, chooser
return prefs
def resolve_runtime(chestnut_available: bool, *, root: Path = ROOT, randomize: bool = False) -> RuntimeSelection:
prefs = randomize_next_start(chestnut_available, root=root) if randomize else preferences(root)
entries = catalog(root)
small, big = prefs["small"], prefs["big"]
small_path = verified_artifact(small, entries, root)
def resolve_runtime(chestnut_available: bool, *, root: Path = ROOT, randomize: bool = False,
plan: RuntimePlan | None = None) -> RuntimeSelection:
if plan is None:
plan = freeze_runtime(root=root, chestnut_available=chestnut_available, randomize=randomize)
elif randomize:
raise ModelError("Randomize before freezing the model runtime")
entries = json.loads(plan.entries_json)
fallback = json.loads(plan.default_json)
small, big = plan.small_id, plan.big_id
if big and entries.get(big, {}).get("_runtime_family") != plan.family:
raise ModelError("Big artifact does not match the selected runtime")
if small != DEFAULT_SMALL and entries.get(small, {}).get("_runtime_family") != plan.family:
raise ModelError("Small artifact does not match the selected runtime")
small_path = verified_artifact(small, entries, root) if entries.get(small, {}).get("_runtime_family") == plan.family else None
shipped = small == DEFAULT_SMALL and small_path is None
if shipped:
small_path = shipped_default()
small_path = shipped_default(plan)
if small_path is None:
small = DEFAULT_SMALL
shipped = True
small_path = shipped_default()
small_path = shipped_default(plan)
if small_path is None:
raise ModelError("Shipped RDFv4 is missing or corrupt; reinstall the validated model package")
big_path = verified_artifact(big, entries, root) if big not in ("", BUNDLED_CURRENT) and chestnut_available else None
allow_big = chestnut_available and big_path is not None
return RuntimeSelection(small, small_path, entries[small]["version"], big, big_path,
return RuntimeSelection(small, small_path, fallback["version"] if shipped else entries[small]["version"], big, big_path,
entries[big]["version"] if big in entries else "current", allow_big,
(DEFAULT_SMALL_SHA256 if shipped else entries[small]["artifact_sha256"]) if small_path is not None else None,
entries[big]["artifact_sha256"] if big_path is not None else None)
(fallback["artifact_sha256"] if shipped else entries[small]["artifact_sha256"]) if small_path is not None else None,
entries[big]["artifact_sha256"] if big_path is not None else None,
shipped_default(plan), fallback["version"], fallback["artifact_sha256"])
class ModelManager:
@@ -391,7 +492,7 @@ class ModelManager:
parts.append(sidecar)
identity = tuple((str(part), part.stat().st_ino, part.stat().st_size,
part.stat().st_mtime_ns, part.stat().st_ctime_ns) for part in parts)
return (0, 0, DEFAULT_SMALL_SIZE, 0, 0, DEFAULT_SMALL_SHA256, DEFAULT_SMALL_SIZE,
return (0, 0, row["artifact_size"], 0, 0, row["artifact_sha256"], row["artifact_size"],
any(part.is_symlink() for part in parts), identity)
st = path.stat()
return (st.st_dev, st.st_ino, st.st_size, st.st_mtime_ns, st.st_ctime_ns,
@@ -417,7 +518,11 @@ class ModelManager:
def _snapshot_installed(self, mid: str, row: dict, variant: str = "standard") -> tuple[bool, bool]:
if mid == DEFAULT_SMALL and variant == "standard" and self._signature(mid, row, variant) is None:
row = {**row, "shipped": True, "artifact_sha256": DEFAULT_SMALL_SHA256, "artifact_size": DEFAULT_SMALL_SIZE}
try:
default = default_record("native02e", self.root)
except (OSError, ValueError, TypeError, KeyError):
return False, False
row = {**row, "shipped": True, "artifact_sha256": default["artifact_sha256"], "artifact_size": default["artifact_size"]}
artifact = artifact_entry(mid, {mid: row}, variant)
if "artifact_sha256" not in artifact:
return False, False
@@ -643,14 +748,17 @@ class ModelManager:
raise ModelError("Unknown model action")
def _refresh(self) -> None:
url = f"{RESOURCE_URL}/manifests/model_names_{GENERATION}.json"
with self.opener(url, timeout=30) as response:
raw = response.read(MAX_MANIFEST + 1)
if len(raw) > MAX_MANIFEST:
raise ModelError("Model catalog is too large")
data = json.loads(raw)
validate_manifest(data)
atomic_json(self.root / "catalog.json", data)
documents = []
for generation, native in ((GENERATION, False), (NATIVE_GENERATION, True)):
with self.opener(f"{RESOURCE_URL}/manifests/model_names_{generation}.json", timeout=30) as response:
raw = response.read(MAX_MANIFEST + 1)
if len(raw) > MAX_MANIFEST:
raise ModelError("Model catalog is too large")
data = json.loads(raw)
validate_manifest(data, native=native)
documents.append((self.root / NATIVE_GENERATION / "catalog.json" if native else self.root / "catalog.json", data))
for path, data in documents:
atomic_json(path, data)
def _work(self, action: str, payload: dict, lock) -> None:
failed = True
@@ -693,7 +801,7 @@ class ModelManager:
digest, received = hashlib.sha256(), 0
with os.fdopen(fd, "wb") as out:
for piece in pieces:
with self.opener(f"{RESOURCE_URL}/models/{GENERATION}/{mid}/{piece}", timeout=30) as response:
with self.opener(f"{RESOURCE_URL}/models/{entry.get('_generation', GENERATION)}/{mid}/{piece}", timeout=30) as response:
while data := response.read(1024 * 1024):
self._check_cancelled()
received += len(data)
+3 -4
View File
@@ -14,7 +14,8 @@ from openpilot.selfdrive.controls.lib.drive_helpers import MIN_SPEED, smooth_val
from openpilot.selfdrive.modeld.constants import ModelConstants, Plan
from openpilot.selfdrive.modeld.helpers import load_oob
from openpilot.starpilot.models.parser import Parser
from openpilot.starpilot.models.catalog import ARTIFACT_ABI, COMPILER_REVISION
from openpilot.starpilot.models.runtime_bridge.runtime import selected_plan
from openpilot.starpilot.models.runtime_selection import require_artifact_contract
from openpilot.starpilot.models.receipt import PreparedArtifact, artifact_identity, prepare_artifact
from openpilot.system.camerad.cameras.nv12_info import get_nv12_info
@@ -63,9 +64,7 @@ def load_verified_model(cam_w: int, cam_h: int, artifact_path: Path, behavior_ve
def validate_artifact(artifact: dict, version: str, camera_size: tuple[int, int]) -> None:
if (not isinstance(artifact, dict) or artifact.get("artifact_abi") != ARTIFACT_ABI or
artifact.get("compiler_revision") != COMPILER_REVISION or artifact.get("format_version") != 1):
raise ValueError("catalog model compiler/ABI mismatch")
require_artifact_contract(artifact, selected_plan())
if version not in SUPPORTED_VERSIONS or artifact.get("behavior_version") != version:
raise ValueError("catalog model behavior version mismatch")
if artifact.get("model_type") not in ("supercombo", "vision_policy", "vision_multi_policy"):
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,281 @@
"""Integrity gate and native02e loader; no tinygrad imports before prepare."""
from contextlib import contextmanager
import atexit
import hashlib
import importlib
import json
import os
from pathlib import Path, PurePosixPath
import stat
import sys
import tempfile
from collections.abc import Callable
from typing import TYPE_CHECKING, Protocol, cast
from openpilot.starpilot.models.runtime_selection import NATIVE_REVISION
from openpilot.starpilot.models.runtime_bridge.bootstrap import (
IMPORT_FAMILIES, _check_source_package, _namespace, _private_cache,
)
if TYPE_CHECKING:
from tinygrad.device import Buffer
class PersistentLoad(Protocol):
persistent_load: Callable[[tuple[int, int]], "Buffer"]
# Deliberately unpublishable until native source/guard and model evidence is sealed.
POLICY_SHA256 = '6608a8280152a1b37c39370942a910c8f34180f4c0626f7ee73340b32a6b6c7e'
ROOT = Path(__file__).resolve().parents[4]
BRIDGE = Path(__file__).parent
_LIBRARIES = {
'libbounded_wait.so': '24ed559861c8636c1026cad9a244854bb2236b5ee8b91bf21c5efbf688fa4a69',
'libusb_guards.so': '66a3dfe48ac17899862ab80160cf6dac2c9b9707234527ba0d808f3b68404114',
}
_role = None
_allowed = {}
_protection = None
def _plain_file(path):
for parent in (*reversed(path.parents), path):
mode = parent.lstat().st_mode
if stat.S_ISLNK(mode):
raise ValueError('Symlink in native runtime input')
if not path.is_file():
raise ValueError('Missing native runtime input')
def _digest(path):
_plain_file(path)
with path.open('rb') as stream:
return hashlib.file_digest(stream, 'sha256').hexdigest()
def _verify_policy():
path = BRIDGE / 'native-policy.json'
_plain_file(path)
raw = path.read_bytes()
if hashlib.sha256(raw).hexdigest() != POLICY_SHA256:
raise RuntimeError('Native runtime policy is not qualified')
policy = json.loads(raw)
required = {'native_guards.py', 'bounded_wait.c', 'usb_guards.c', *_LIBRARIES}
if (policy.get('compiler_revision') != NATIVE_REVISION or policy.get('native_abi') != 'linux-aarch64-64bit' or
policy.get('host_runtime_dev') not in ('CPU:LLVM', 'CPU:CLANG') or
not isinstance(policy.get('files'), dict) or set(policy['files']) != required or
not isinstance(policy.get('vendor_files'), dict) or not policy['vendor_files']):
raise ValueError('Native runtime identity mismatch')
for name, expected in policy['files'].items():
if _digest(BRIDGE / name) != expected or name in _LIBRARIES and expected != _LIBRARIES[name]:
raise ValueError('Native guard source/artifact changed')
if _digest(BRIDGE.parent / 'catalog-native02e.json') != policy.get('catalog_sha256'):
raise ValueError('Native stock release metadata changed')
vendor = ROOT / 'tinygrad_repo'
expected_files = policy['vendor_files']
actual = set()
for directory, dirs, files in os.walk(vendor, followlinks=False):
dirs[:] = [name for name in dirs if name not in ('__pycache__', '.git')]
for name in dirs:
if (Path(directory) / name).is_symlink():
raise ValueError('Symlink in native runtime source')
for name in files:
if name.endswith(('.pyc', '.pyo')):
raise ValueError('Standalone native vendor bytecode is not source-pinned')
if name == 'AGENTS.md':
continue
actual.add((Path(directory) / name).relative_to(vendor).as_posix())
if actual != set(expected_files):
raise ValueError('Native vendor inventory changed')
for name, expected in expected_files.items():
relative = PurePosixPath(name)
if relative.is_absolute() or '..' in relative.parts or '\\' in name or str(relative) != name:
raise ValueError('Invalid native vendor path')
if _digest(vendor / name) != expected:
raise ValueError('Native vendor source changed')
return policy
def _remove_empty_prefix(path):
try:
os.rmdir(path)
except OSError:
pass
def prepare(role, plan):
"""Run before any tinygrad import; root seals policy after actual qualification."""
global _role, _allowed
if role not in ('driving', 'monitoring') or role == 'driving' and (plan is None or plan.family != 'native02e'):
raise ValueError('Invalid native runtime role/plan')
if _role is not None:
raise RuntimeError('Native runtime was already selected')
if any(name == family or name.startswith(family + '.') for name in sys.modules for family in IMPORT_FAMILIES):
raise RuntimeError('Select native runtime before importing tinygrad')
policy = _verify_policy()
from openpilot.starpilot.models.manager import SHIPPED_MODELS, native_stock
stock = native_stock()
if role == 'monitoring':
allowed = {str(SHIPPED_MODELS / name): row for name, row in stock.items()}
else:
default = json.loads(plan.default_json)
entries = json.loads(plan.entries_json)
allowed = {default['path']: default}
small = entries.get(plan.small_id)
if small is not None and small.get('_runtime_family') == 'native02e':
allowed[small['_artifact_path']] = small
if plan.big_id:
row = entries[plan.big_id]
allowed[row['_artifact_path']] = row
cache = Path('/data/starpilot-model-runtime') / NATIVE_REVISION / role / POLICY_SHA256
_private_cache(cache)
_private_cache(cache / 'xdg')
bytecode_prefix = Path(tempfile.mkdtemp(prefix='python-', dir=cache))
_private_cache(bytecode_prefix)
if any(bytecode_prefix.iterdir()):
raise RuntimeError('Native bytecode prefix is not empty')
atexit.register(_remove_empty_prefix, bytecode_prefix)
tg_root = ROOT / 'tinygrad_repo'
sys.path[:0] = [str(ROOT), str(tg_root)]
sys.dont_write_bytecode = True
# A fresh private prefix isolates READS too; dont_write_bytecode alone does not.
sys.pycache_prefix = str(bytecode_prefix)
os.environ.pop('PYTHONPYCACHEPREFIX', None)
os.environ['HCQ_RUNTIME_DEV'] = policy['host_runtime_dev']
os.environ.update(CACHEDB=str(cache / 'tinygrad.db'), XDG_CACHE_HOME=str(cache / 'xdg'), PYTHONDONTWRITEBYTECODE='1')
sys.modules['tinygrad_repo'] = _namespace('tinygrad_repo', tg_root)
sys.modules['extra'] = _namespace('extra', tg_root / 'extra')
importlib.invalidate_caches()
_check_source_package('tinygrad', tg_root / 'tinygrad/__init__.py')
_check_source_package('examples', tg_root / 'examples/__init__.py')
_role, _allowed = role, allowed
return {'role': role, 'family': 'native02e', 'compiler_revision': NATIVE_REVISION,
'manifest_sha256': POLICY_SHA256, 'runtime_files': len(policy['vendor_files']),
'tinygrad_origin': str(tg_root / 'tinygrad/__init__.py'), 'cache': str(cache)}
def _guard():
global _protection
if _role is None:
raise RuntimeError('Native runtime is not prepared')
if _protection is None:
_verify_policy()
from openpilot.starpilot.models.runtime_bridge import native_guards
_protection = native_guards.install(_role, BRIDGE / 'libbounded_wait.so', BRIDGE / 'libusb_guards.so')
return _protection
@contextmanager
def _verified(path):
path = Path(path)
record = _allowed.get(str(path).removesuffix('.unchunked'))
if record is None:
raise ValueError('Artifact is outside the frozen native selection')
fd = os.open(path, os.O_RDONLY | os.O_NONBLOCK | os.O_NOFOLLOW | os.O_CLOEXEC)
with os.fdopen(fd, 'rb') as stream:
before = os.fstat(stream.fileno())
def identity(value):
return value.st_dev, value.st_ino, value.st_size, value.st_mtime_ns, value.st_ctime_ns
if (not stat.S_ISREG(before.st_mode) or before.st_size != record['artifact_size'] or
hashlib.file_digest(stream, 'sha256').hexdigest() != record['artifact_sha256']):
raise ValueError('Native artifact identity mismatch')
stream.seek(0)
def unchanged():
named = path.lstat()
if (not stat.S_ISREG(named.st_mode) or identity(before) != identity(os.fstat(stream.fileno())) or
identity(before) != identity(named)):
raise ValueError('Native artifact changed while loading')
unchanged()
yield stream, unchanged
unchanged()
def _load_driving(stream, unchanged):
import io
import mmap
import pickle
import struct
from tinygrad import Device
from tinygrad.device import Buffer, HCQ_RUNTIME_DEV
length_raw = stream.read(8)
if len(length_raw) != 8:
raise ValueError('Missing native OOB envelope')
length = struct.unpack('<q', length_raw)[0]
if not 0 < length <= os.fstat(stream.fileno()).st_size - 8:
raise ValueError('Invalid native OOB envelope')
opcodes = stream.read(length)
if stream.tell() == os.fstat(stream.fileno()).st_size:
unchanged()
return pickle.loads(opcodes)
# Map the already authenticated inode. Do not reopen a path or use a cached
# DISK:/proc/self/fd device whose descriptor number can be reused later.
mapping = mmap.mmap(stream.fileno(), 0, access=mmap.ACCESS_COPY)
view = memoryview(mapping)[stream.tell():]
buffers = Buffer(HCQ_RUNTIME_DEV.device, len(view), opaque=view)
arena, chunk_size = Buffer(Device.DEFAULT, buffers.nbytes, preallocate=True), 8 << 20
unchanged()
for off in range(0, buffers.nbytes, chunk_size):
size = min(chunk_size, buffers.nbytes - off)
arena.view(size, off).ensure_allocated().copy_from(buffers.view(size, off).ensure_allocated())
Device.default.synchronize()
unchanged()
del buffers
view.release()
mapping.close()
unpickler = pickle.Unpickler(io.BytesIO(opcodes))
cast("PersistentLoad", unpickler).persistent_load = lambda record: arena.view(*record)
return unpickler.load()
def _load_monitoring(stream, unchanged):
import io
import pickle
import struct
from tinygrad import Device
from tinygrad.device import Buffer
data = stream.read()
unchanged()
if len(data) < 8:
raise ValueError('Missing native monitoring envelope')
length = struct.unpack('<q', data[:8])[0]
if not 0 < length <= len(data) - 8:
raise ValueError('Invalid native monitoring envelope')
end = 8 + length
if end == len(data):
unchanged()
return pickle.loads(data[8:end])
arena = Buffer(Device.DEFAULT, len(data) - end, initial_value=data[end:])
unchanged()
unpickler = pickle.Unpickler(io.BytesIO(data[8:end]))
cast("PersistentLoad", unpickler).persistent_load = lambda record: arena.view(*record)
return unpickler.load()
def load_oob(path, chestnut=False):
if _role is None or chestnut and _role != 'driving':
raise ValueError('Invalid native artifact role')
from tinygrad import Context, Device
from tinygrad.runtime.ops_qcom import QCOMDevice
protection = _guard()
with _verified(path) as (stream, unchanged), Context(DEV='USB+AMD:LLVM' if chestnut else 'QCOM'):
if not isinstance(Device['QCOM'], QCOMDevice):
raise RuntimeError('Expected the native QCOM device')
artifact = _load_monitoring(stream, unchanged) if _role == 'monitoring' else _load_driving(stream, unchanged)
return protection.artifact(artifact, chestnut=chestnut)
def load_warp(path):
if _role != 'monitoring':
raise ValueError('Native warp loader requires monitoring role')
import pickle
from tinygrad import Context, Device
from tinygrad.runtime.ops_qcom import QCOMDevice
protection = _guard()
with _verified(path) as (stream, unchanged), Context(DEV='QCOM'):
if not isinstance(Device['QCOM'], QCOMDevice):
raise RuntimeError('Expected the native QCOM device')
data = stream.read()
unchanged()
artifact = pickle.loads(data)
if not isinstance(artifact, dict) or set(artifact) != {'metadata', 'run', 'input_specs'}:
raise ValueError('Unexpected native monitoring warp layout')
return protection.warp(artifact['run'])
@@ -0,0 +1,593 @@
"""Compile-only port of the existing native guards to pinned tinygrad 02e.
PRIVATE AND UNQUALIFIED. Stored artifacts are prepared offline. Inference only
verifies their guarded form and lets the ordinary HCQ linker bind fresh addresses.
"""
from collections import Counter
from dataclasses import replace
import ctypes
import hashlib
import itertools
from pathlib import Path
import struct
from tinygrad import Device
from tinygrad.device import Buffer, Compiled, HCQ_RUNTIME_DEV
from tinygrad.dtype import AddrSpace, dtypes
from tinygrad.engine import realize
from tinygrad.renderer.cstyle import ClangRenderer
from tinygrad.runtime.autogen import libusb
from tinygrad.runtime.support import hcq2
from tinygrad.runtime.support.c import DLL
from tinygrad.uop.ops import Ops, PatternMatcher, ProgramInfo, UOp, UPat, graph_rewrite, pm_renumber_slots
from tinygrad.schedule.prepare import resolve_function, pm_inline_calls
FORMAT = "starpilot_native02e_guard_v1"
REVISION = "02e698160eed1751738212144a7ea2cfb160b3f1"
FENCE_SHA256 = "24ed559861c8636c1026cad9a244854bb2236b5ee8b91bf21c5efbf688fa4a69"
USB_SHA256 = "66a3dfe48ac17899862ab80160cf6dac2c9b9707234527ba0d808f3b68404114"
FFI_TAG = "starpilot_native02e_libusb_functions"
FFI_NAMES = ("libusb_control_transfer", "libusb_bulk_transfer", "libusb_submit_transfer", "libusb_handle_events_timeout")
WRAPPERS = dict(zip(FFI_NAMES, ("sp_control", "sp_bulk", "sp_submit", "sp_events"), strict=True))
GUARD_NAMES = frozenset((*WRAPPERS.values(), "sp_loop_touch", "sp_loop_check", "starpilot_bounded_wait"))
_PINNED_NATIVE = {"libbounded_wait.so": FENCE_SHA256, "libusb_guards.so": USB_SHA256}
_INSTALLATION = None
def _current_native_ffi_buffer(device):
# Called at link time, not captured when tinygrad compiles a matcher callback.
# The module-level indirection keeps a Protection/Buffer (and its addresses)
# out of deconstruct_function's copied/pickled callback globals.
protection = _INSTALLATION
if protection is None or protection.role != "driving" or protection.ffi_buffer is None:
raise ValueError("Native function table has no active driving installation")
buffer = protection.ffi_buffer
if device != HCQ_RUNTIME_DEV.device or buffer.device != device or buffer.nbytes != len(FFI_NAMES) * 8:
raise ValueError("Native function table runtime storage changed")
return buffer
def _resolve_native_ffi(b):
if b.op is not Ops.ALLOC or b.tag != FFI_TAG or b.dtype is not dtypes.uint64 or b.shape != (len(FFI_NAMES),):
raise ValueError("Native function table binding changed")
return _current_native_ffi_buffer(b.device)
def _nodes(root):
return tuple(root.toposort(enter_calls=True))
def _payloads(root):
cpu = {u.src[-1] for u in _nodes(root)
if u.op is Ops.PROGRAM and isinstance(u.arg, ProgramInfo) and u.arg.target.device == "CPU"}
return Counter(hashlib.sha256(u.arg).hexdigest() for u in _nodes(root)
if u.op is Ops.BINARY and isinstance(u.arg, bytes) and u not in cpu)
def _constant(u, value):
return u.vmin == u.vmax == value
def _name(call):
return call.body.arg.name if call.op is Ops.CALL and call.body.op is Ops.CUSTOM_FUNCTION else None
def _calls(root):
return tuple(u for u in _nodes(root) if _name(u) is not None)
def _predicate_leaves(condition, mandatory=True):
"""Boolean terms only: do not walk through load/callback side-effect inputs."""
if condition.dtype is not dtypes.bool:
raise ValueError("Native polling predicate is not boolean")
if condition.op in (Ops.AND, Ops.OR):
if len(condition.src) != 2:
raise ValueError("Unknown native boolean connector")
required = mandatory and condition.op is Ops.AND
return tuple(term for child in condition.src for term in _predicate_leaves(child, required))
if condition.op not in (Ops.CMPLT, Ops.CMPEQ, Ops.CMPNE):
raise ValueError("Unknown native polling predicate")
return ((condition, mandatory),)
def _pending_status(condition):
# UOp.eq is represented as (value != 255) != True before simplification.
if (condition.op is Ops.CMPNE and condition.src[1].dtype is dtypes.bool and
_constant(condition.src[1], True)):
inner = condition.src[0]
return (inner.op is Ops.CMPNE and inner.src[0].op is Ops.LOAD and
inner.src[0].dtype is dtypes.uint32 and _constant(inner.src[1], 0xff))
return (condition.op is Ops.CMPEQ and condition.src[0].op is Ops.LOAD and
condition.src[0].dtype is dtypes.uint32 and _constant(condition.src[1], 0xff))
def _polling_condition(condition):
leaves = _predicate_leaves(condition)
timers = [(term, limit, required) for term, required in leaves if term.op is Ops.CMPLT
for limit in (1000, 30000) if _constant(term.src[1], limit)]
if len(timers) == 1 and timers[0][2]:
timer, limit, _ = timers[0]
# The exact recognized deadline is delegated to the fixed native C guard.
# All status/sequence/error predicates, including their dependencies, stay.
return condition.substitute({timer: UOp.const(True, dtypes.bool)}, enter_calls=True), limit * 1_000_000
if not timers and sum(required and _pending_status(term) for term, required in leaves) == 1:
return condition, 10_000_000_000
raise ValueError("Unqualified native USB polling condition")
def _checked_poll_exit(condition):
"""Exactly a uint64 guard result converted to boolean, before/after lowering."""
if condition.dtype is not dtypes.bool:
return False
if condition.op is Ops.CAST and len(condition.src) == 1:
call = condition.src[0]
elif condition.op is Ops.CMPNE and len(condition.src) == 2:
call, zero = condition.src
if (zero.dtype is not dtypes.uint64 or
not (zero.op is Ops.CONST and _constant(zero, 0) or
zero.op is Ops.CAST and len(zero.src) == 1 and
zero.src[0].op is Ops.CONST and _constant(zero.src[0], 0))):
return False
else:
return False
return call.dtype is dtypes.uint64 and len(call.src) == 3 and _name(call) == "sp_loop_check"
def _weak(root):
return tuple(u for u in _nodes(root) if u.op is Ops.RANGE and u.dtype is dtypes.void)
def _host(call):
if call.op is not Ops.CALL or not isinstance(call.arg.aux, hcq2.HCQInfo):
raise ValueError("Expected actual 02e HCQ call")
if call.arg.aux.device not in (("QCOM",), ("AMD",)):
raise ValueError("Unsupported native guard device")
program = call.body
if (program.op is not Ops.PROGRAM or len(program.src) != 4 or
not isinstance(program.arg, ProgramInfo) or program.arg.target.device != "CPU" or
program.src[-1].op is not Ops.BINARY):
raise ValueError("Expected complete compiled CPU host")
sink = program.src[0]
if sink.op is not Ops.SINK or sink.arg.name != "hcq_submit":
raise ValueError("Unexpected CPU submission source")
if any(u.op is Ops.CALL and u.body.op in (Ops.SINK, Ops.PROGRAM) for u in _nodes(sink)):
raise ValueError("Nested submission was not completely lowered")
return program, sink
def _proof(call):
program, sink = _host(call)
return {
"device": list(call.arg.aux.device),
"cpu_target": str(program.arg.target),
"cpu_binary_sha256": hashlib.sha256(program.src[3].arg).hexdigest(),
"cpu_source_sha256": hashlib.sha256(program.src[2].arg.encode()).hexdigest(),
"opaque_payloads": dict(_payloads(sink)),
"arguments": len(call.src) - 1,
"native_calls": dict(Counter(_name(u) for u in _calls(sink))),
}
def _load_library(path, expected, key, symbols):
path = Path(path)
if path.is_symlink() or not path.is_file():
raise ValueError("Missing qualified native guard library")
raw = path.read_bytes()
if (hashlib.sha256(raw).hexdigest() != expected or len(raw) < 20 or
raw[:6] != b"\x7fELF\x02\x01" or struct.unpack_from("<HH", raw, 16) != (3, 183)):
raise ValueError("Native guard library identity or ABI changed")
if key in DLL._loaded_:
raise ValueError("Native guard library key already registered")
lib = ctypes.CDLL(str(path.resolve()))
for symbol in symbols:
expected_address = ctypes.cast(getattr(lib, symbol), ctypes.c_void_p).value
if not expected_address:
raise ValueError("Missing guard export")
for previous in DLL._loaded_.values():
try:
other = ctypes.cast(getattr(previous, symbol), ctypes.c_void_p).value
except AttributeError:
continue
if other != expected_address:
raise ValueError("Native guard symbol collision")
DLL._loaded_[key] = lib
return lib
def _fresh_scope(c):
if not c.is_inline_call:
return None
# Do not enter nested function bodies or substitute their PARAM namespace.
# Freshen this invocation's REG storage and ranges BEFORE argument binding,
# so distinct locals cannot hash-cons together after PARAM substitution.
body = graph_rewrite(c.body, pm_renumber_slots, ctx=UOp.unique_num, walk=True)
return resolve_function(c.replace(src=(body, *c.src[1:])))
_INLINE = PatternMatcher([(UPat(Ops.CALL, name='c'), _fresh_scope)]) + pm_inline_calls
def inline_host_sink(sink):
if sink.op is not Ops.SINK or sink.arg is None or sink.arg.name != 'hcq_submit':
raise ValueError('Expected fresh outer native hcq_submit SINK')
outer_params = {u for u in sink.toposort() if u.op is Ops.PARAM}
flat = sink
for _ in range(64):
pending = [u for u in flat.toposort(enter_calls=True)
if u.op is Ops.CALL and u.body.op in (Ops.SINK, Ops.LINEAR, Ops.PROGRAM)]
if not pending:
break
if any(not u.is_inline_call for u in pending):
raise ValueError('Unexpected already-outlined or non-inline host function')
updated = graph_rewrite(flat, _INLINE, walk=True)
if updated is flat:
raise ValueError('Host function inlining made no progress')
flat = updated
else:
raise ValueError('Host function nesting exceeds bounded inlining limit')
if any(u.op is Ops.PARAM and u not in outer_params for u in flat.toposort()):
raise ValueError('An outlined function-local PARAM escaped binding')
flat = graph_rewrite(flat, pm_renumber_slots, ctx=itertools.count(), walk=True)
ranges = [u for u in flat.toposort() if u.op is Ops.RANGE]
regs = [u for u in flat.toposort() if u.op is Ops.BUFFER and u.addrspace is AddrSpace.REG]
if len({u.arg for u in ranges}) != len(ranges) or len({u.arg.slot for u in regs}) != len(regs):
raise ValueError('Flattened local identities are not unique')
if sink.arg != flat.arg or sink.tag != flat.tag:
raise ValueError('Outer host source identity changed')
return flat
def _native_ffi_slot(sink, argument_count):
params = tuple(u for u in _nodes(sink) if u.op is Ops.PARAM)
slot = min((u.arg.slot for u in params if u.addrspace is AddrSpace.ALU and u.arg.slot >= 0), default=argument_count)
if not 0 <= slot <= argument_count or any(u.addrspace is not AddrSpace.ALU and u.arg.slot >= slot for u in params):
raise ValueError("Native host pointer/scalar parameter order changed")
return slot
def _shift_native_params(sink, first_slot, argument_count):
original = tuple(u for u in _nodes(sink) if u.op is Ops.PARAM)
moving = tuple(u for u in original if u.arg.slot >= first_slot)
if not moving:
return sink
# substitute recursively rewrites replacements. Disjoint temporary slots
# make both phases simultaneous; no original or final slot is a temp key.
temporary_start = max(argument_count, *(u.arg.slot for u in original)) + 2
temporary = {u: u.replace(arg=replace(u.arg, slot=temporary_start + i)) for i, u in enumerate(moving)}
final = {temporary[u]: u.replace(arg=replace(u.arg, slot=u.arg.slot + 1)) for u in moving}
shifted = sink.substitute(temporary, enter_calls=True).substitute(final, enter_calls=True)
expected = {u.replace(arg=replace(u.arg, slot=u.arg.slot + 1)) if u in temporary else u for u in original}
actual = {u for u in _nodes(shifted) if u.op is Ops.PARAM}
if actual != expected or len(actual) != len(original):
raise ValueError("Native host parameter renaming lost an original binding")
return shifted
def _verify_cpu_parameter_abi(program):
# Clang emits PARAM declarations in the compiled LINEAR's order. CPUProgram
# invokes every globals pointer first, then every ProgramInfo.vars scalar.
linear = program.src[1]
if linear.op is not Ops.LINEAR:
raise ValueError("Native host has no compiled parameter declaration order")
params = tuple(u for u in linear.src if u.op is Ops.PARAM)
expected = (*program.arg.globals, *(u.arg.slot for u in program.arg.vars))
if tuple(u.arg.slot for u in params) != expected:
raise ValueError("Native CPU parameter ABI is misordered")
boundary = len(program.arg.globals)
if (any(u.addrspace is AddrSpace.ALU for u in params[:boundary]) or
any(u.addrspace is not AddrSpace.ALU for u in params[boundary:])):
raise ValueError("Native CPU parameter ABI mixes pointers and scalars")
def _fresh_host(call):
if call.op is not Ops.CALL or not isinstance(call.arg.aux, hcq2.HCQInfo):
raise ValueError('Expected fresh actual 02e HCQ call')
if call.arg.aux.device not in (('QCOM',), ('AMD',)) or call.body.op is not Ops.SINK:
raise ValueError('Native guards must precede CPU compilation/outlining')
if call.body.arg is None or call.body.arg.name != 'hcq_submit':
raise ValueError('Unknown fresh CPU host source')
return call.body
class Protection:
def __init__(self, role, fence_library, usb_library):
if role not in ("driving", "monitoring"):
raise ValueError("Unknown native model role")
if HCQ_RUNTIME_DEV.device.split(":")[0] != "CPU":
raise ValueError("Native guard requires actual CPU helper target")
renderer = Device[HCQ_RUNTIME_DEV.device]._select_renderer(HCQ_RUNTIME_DEV)
if not isinstance(renderer, ClangRenderer):
raise ValueError("Native guard requires its qualified Clang host renderer")
self.role, self.renderer, self.records = role, renderer, []
self.fence_library = _load_library(fence_library, FENCE_SHA256, "starpilot_native02e_fence",
("starpilot_bounded_wait",))
self.usb_library = None
self.ffi_buffer = None
self.ffi = None
if role == "driving":
self.usb_library = _load_library(usb_library, USB_SHA256, "starpilot_native02e_usb",
(*WRAPPERS.values(), "sp_loop_touch", "sp_loop_check"))
fields = {name: (typ, offset) for name, typ, offset, *_ in libusb.struct_libusb_transfer._real_fields_}
if any(fields[name][1] != offset or ctypes.sizeof(fields[name][0]) != 4
for name, offset in (("status", 16), ("length", 20), ("actual_length", 24))):
raise ValueError("Unqualified native libusb transfer layout")
if ctypes.sizeof(ctypes.c_void_p) != 8 or ctypes.sizeof(ctypes.c_ulong) != 8:
raise ValueError("Unqualified native host ABI")
addresses = [ctypes.cast(getattr(libusb.dll, name), ctypes.c_void_p).value for name in FFI_NAMES]
if not all(addresses):
raise ValueError("Missing native libusb functions")
self.ffi_buffer = Buffer(HCQ_RUNTIME_DEV.device, len(addresses) * 8,
initial_value=b"".join(address.to_bytes(8, "little") for address in addresses))
self.ffi = UOp.alloc((len(addresses),), dtypes.uint64, 0, device=HCQ_RUNTIME_DEV.device).rtag(FFI_TAG)
previous = Compiled.pm_bufferize
Compiled.pm_bufferize = previous + PatternMatcher([(UPat(Ops.ALLOC, tag=FFI_TAG, name="b"), _resolve_native_ffi)])
self.identity = {
"format": FORMAT, "compiler_revision": REVISION,
"implementation_sha256": hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),
"fence_sha256": FENCE_SHA256, "usb_sha256": USB_SHA256,
"host_target": str(self.renderer.target),
}
@staticmethod
def _native(name, args, dtype):
return UOp.custom_function(name, dtype=dtype).call(*args)
def _compile(self, original, rewritten, args):
before = _payloads(_fresh_host(original))
if _payloads(rewritten) != before:
raise ValueError("Opaque GPU payload changed before CPU compilation")
updated = original.replace(src=(rewritten, *args))
if updated.device != HCQ_RUNTIME_DEV.device:
raise ValueError("Guard rewrite attempted non-CPU compilation")
compiled = realize.lower_and_compile(UOp(Ops.LINEAR, src=(updated,))).src[0]
program, sink = _host(compiled)
if program.arg.target != self.renderer.target or _payloads(sink) != before:
raise ValueError("CPU target or opaque GPU payload changed")
_verify_cpu_parameter_abi(program)
self._verify_call(compiled)
self.records.append(_proof(compiled))
return compiled
def _qcom(self, call, sink):
loops, replacements = _weak(sink), {}
if len(loops) != 1:
raise ValueError("Expected exactly one native QCOM fence")
loop = loops[0]
ends = [u for u in _nodes(sink) if u.op is Ops.BACKEDGE and u.src[1] is loop]
if len(ends) != 1:
raise ValueError("Unknown native QCOM loop boundary")
done, _, condition = ends[0].src
if done.op is not Ops.LOAD or done.dtype is not dtypes.uint64:
raise ValueError("Unknown native QCOM timeline load")
comparisons = [u for u in _nodes(condition) if u.op is Ops.CMPLT and u.src[0] is done]
timers = [u for u in _nodes(condition) if u.op is Ops.CMPLT and _constant(u.src[1], 30000)]
if len(comparisons) != 1 or len(timers) != 1:
raise ValueError("Unknown native QCOM deadline condition")
target = comparisons[0].src[1]
if target.dtype is not dtypes.uint64 or loop in target.ranges:
raise ValueError("Unknown native QCOM fence target")
index = done.src[0]
if index.op is not Ops.INDEX or len(index.src) != 2 or not _constant(index.src[1], 0):
raise ValueError("Unknown native QCOM signal address")
signal = index.src[0].without_after
arguments = realize.get_call_arg_uops(call)
if signal.op is not Ops.PARAM or not 0 <= signal.arg.slot < len(arguments):
raise ValueError("Native QCOM timeline is not an outer parameter")
outer = arguments[signal.arg.slot].without_after
if outer.op is not Ops.ALLOC or outer.tag != "timeline" or outer.device != "QCOM":
raise ValueError("Timeline is not the native QCOM allocation")
pointer = index.substitute({loop: UOp(Ops.NOOP)}, enter_calls=True)
replacements[ends[0]] = self._native("starpilot_bounded_wait", (pointer, target), dtypes.uint64)
rewritten = sink.substitute(replacements, enter_calls=True)
if _weak(rewritten):
raise ValueError("An unguarded QCOM polling range remains")
return self._compile(call, rewritten, call.src[1:])
def _usb(self, call, sink):
if self.role != "driving" or self.ffi is None or not Device["AMD"].is_usb:
raise ValueError("Expected the actual native USB AMD device")
return self._usb_rewrite(call, sink)
def _usb_rewrite(self, call, sink):
args = list(call.src[1:])
argument_count = len(realize.get_call_arg_uops(call))
if any(not a.is_bound_var for a in args[argument_count:]) or any(a.is_bound_var for a in args[:argument_count]):
raise ValueError("Native bound-variable argument order changed")
ffi_slot = _native_ffi_slot(sink, argument_count)
sink = _shift_native_params(sink, ffi_slot, argument_count)
pointer = UOp.param(ffi_slot, dtypes.uint64, (len(FFI_NAMES),), HCQ_RUNTIME_DEV.device, name="native_usb_functions")
loops = _weak(sink)
if not 1 <= len(loops) < 256 or len({r.arg for r in loops}) != len(loops):
raise ValueError("Unknown native USB polling structure")
specifications, ends = {}, {}
for slot, loop in enumerate(loops):
boundaries = [u for u in _nodes(sink) if u.op is Ops.BACKEDGE and u.src[1] is loop]
if len(boundaries) != 1:
raise ValueError("Unknown native USB loop boundary")
end = boundaries[0]
condition, timeout = _polling_condition(end.src[2])
specifications[loop] = (slot, timeout)
ends[end] = condition
calls = _calls(sink)
if any(_name(u) not in {*FFI_NAMES, "memcpy", "clock_gettime"} for u in calls):
raise ValueError("Unqualified native USB callback")
def scalar64(u):
return UOp(Ops.CUSTOMI, src=(u,), arg=("((unsigned long)({0}))", dtypes.uint64))
substitutions, touched = {}, set()
for callback in calls:
name = _name(callback)
if name not in WRAPPERS:
continue
touches = []
for loop, (slot, timeout) in specifications.items():
token = next((a for a in callback.src[1:] if loop in a.ranges), None)
if token is not None:
touches.append(self._native("sp_loop_touch", (
UOp.const(slot, dtypes.uint64), UOp.const(timeout, dtypes.uint64), scalar64(token)), dtypes.uint64))
touched.add(loop)
marker = sum(touches[1:], touches[0]) if touches else UOp.const(0, dtypes.uint64)
function = pointer.index(FFI_NAMES.index(name)).load()
substitutions[callback] = self._native(WRAPPERS[name], (
function, *(scalar64(a) for a in callback.src[1:]), marker), dtypes.int32)
if touched != set(specifications) or not substitutions:
raise ValueError("A native USB polling loop has no entry guard")
sink = sink.substitute(substitutions, enter_calls=True)
# Substitution can rebuild range identities; immutable unique range args
# are the previously checked stable lookup keys.
by_arg = {loop.arg: (slot, timeout) for loop, (slot, timeout) in specifications.items()}
expected_conditions = {end.src[1].arg: condition for end, condition in ends.items()}
replacements = {}
for end in _nodes(sink):
if end.op is Ops.BACKEDGE and end.src[1].arg in by_arg:
slot, _ = by_arg[end.src[1].arg]
condition = expected_conditions[end.src[1].arg].substitute(substitutions, enter_calls=True)
check = self._native("sp_loop_check", (condition.cast(dtypes.uint64), UOp.const(slot, dtypes.uint64)), dtypes.uint64)
replacements[end] = end.replace(src=(end.src[0], end.src[1], check.cast(dtypes.bool)))
if len(replacements) != len(loops):
raise ValueError("Native USB loop protection is incomplete")
rewritten = sink.substitute(replacements, enter_calls=True)
args.insert(ffi_slot, self.ffi)
if len(args) != len(call.src) or any(args[i + (i >= ffi_slot)] is not original for i, original in enumerate(call.src[1:])):
raise ValueError("Native host call argument insertion lost an original binding")
return self._compile(call, rewritten, args)
def protect_call(self, call):
original = _fresh_host(call)
sink = inline_host_sink(original)
if _payloads(sink) != _payloads(original):
raise ValueError("Host scope binding changed opaque payloads")
if any(_name(u) in GUARD_NAMES for u in _calls(sink)):
raise ValueError("Refusing partial or repeated native protection")
return self._qcom(call, sink) if call.arg.aux.device == ("QCOM",) else self._usb(call, sink)
def _verify_call(self, call):
program, sink = _host(call)
if program.arg.target != self.renderer.target:
raise ValueError("Native CPU target changed")
_verify_cpu_parameter_abi(program)
calls = _calls(sink)
if call.arg.aux.device == ("QCOM",):
if _weak(sink) or sum(_name(u) == "starpilot_bounded_wait" for u in calls) != 1:
raise ValueError("QCOM native guard proof missing")
else:
if self.role != "driving" or not Device["AMD"].is_usb:
raise ValueError("Native Chestnut identity missing")
if any(_name(u) in FFI_NAMES for u in calls):
raise ValueError("An unchecked native USB function remains")
weak = _weak(sink)
boundaries = [u for u in _nodes(sink) if u.op is Ops.BACKEDGE and u.src[1] in weak]
if not weak or len(boundaries) != len(weak):
raise ValueError("Native USB polling proof missing")
for end in boundaries:
cond = end.src[2]
if not _checked_poll_exit(cond):
raise ValueError("An unchecked native polling exit remains")
if not any(_name(u) == "sp_loop_touch" for u in calls):
raise ValueError("Native polling entry proof missing")
markers = [u for u in _nodes(UOp.sink(*call.src[1:])) if u.op is Ops.ALLOC and u.tag == FFI_TAG]
if len(markers) != 1 or markers[0].dtype is not dtypes.uint64 or markers[0].shape != (len(FFI_NAMES),):
raise ValueError("Fresh native function table marker missing")
return _proof(call)
def _prepare(self, jit):
# The only preparation path is fresh HCQ re-encoding from authenticated
# pre-HCQ input. Stored outlined PROGRAMs are never reverse-inlined.
self._verify(jit)
return jit
def _verify(self, jit):
cap = getattr(jit, "captured", None)
if cap is None or "linear" in cap.__dict__:
raise ValueError("Native guard verification must precede captured execution/linking")
for entry in cap._linear.src:
call = entry.without_after
if call.op is not Ops.CALL or not (call.body.op is Ops.STORE or isinstance(call.arg.aux, hcq2.HCQInfo)):
raise ValueError("Unknown native captured schedule entry")
calls = [u for u in _nodes(cap._linear) if u.op is Ops.CALL and isinstance(u.arg.aux, hcq2.HCQInfo)]
if not calls:
raise ValueError("Missing native guarded submission")
return [self._verify_call(call) for call in calls]
@staticmethod
def _capture_items(artifact):
return [(key, value) for key, value in artifact.items() if getattr(value, "captured", None) is not None]
def prepare_artifact(self, artifact, chestnut=False):
items = self._capture_items(artifact)
expected = 3 if self.role == "driving" else 1
if len(items) != expected or "_starpilot_native_guard" in artifact:
raise ValueError("Unknown native artifact capture count or repeated preparation")
if self.role == "driving" and (artifact.get("execution_device") != ("AMD" if chestnut else "QCOM") or
artifact.get("warp_device") != "QCOM"):
raise ValueError("Native driving backend contract changed")
for _, jit in items:
self._prepare(jit)
artifact["_starpilot_native_guard"] = {
"identity": self.identity,
"captures": {repr(key): self._verify(jit) for key, jit in items},
}
return artifact
def artifact(self, artifact, chestnut=False):
items = self._capture_items(artifact)
expected = 3 if self.role == "driving" else 1
if len(items) != expected:
raise ValueError("Unknown native artifact capture count")
proof = artifact.get("_starpilot_native_guard")
if not isinstance(proof, dict) or proof.get("identity") != self.identity:
raise ValueError("Native artifact guard identity changed or missing")
actual = {repr(key): self._verify(jit) for key, jit in items}
if actual != proof.get("captures"):
raise ValueError("Native compiled host proof changed")
if self.role == "driving" and (artifact.get("execution_device") != ("AMD" if chestnut else "QCOM") or
artifact.get("warp_device") != "QCOM"):
raise ValueError("Native driving backend contract changed")
return artifact
def prepare_warp(self, jit):
return self._prepare(jit)
def warp(self, jit):
# The loader must authenticate the complete known stock warp file first.
self._verify(jit)
return jit
def install_fresh_guard(self):
original = hcq2.lower_and_compile
if original is not realize.lower_and_compile:
raise ValueError("Native host compiler was already replaced")
def guarded(linear, *args, **kwargs):
replacements = {}
for call in _nodes(linear):
if call.op is not Ops.CALL or not isinstance(call.arg.aux, hcq2.HCQInfo):
continue
if call.body.op is Ops.SINK:
replacements[call] = self.protect_call(call)
elif call.body.op is Ops.PROGRAM:
self._verify_call(call)
else:
raise ValueError("Unknown fresh native host body")
prepared = linear.substitute(replacements, enter_calls=True)
for call in _nodes(prepared):
if call.op is Ops.CALL and realize._get_call_to_compile(call) is not None:
raise ValueError("Residual unqualified compilation after native guards")
# Every compiled host is guarded before returning to HCQ. This does not
# execute the intermediate graph or ask a GPU compiler to rebuild kernels.
return original(prepared, *args, **kwargs)
hcq2.lower_and_compile = guarded
def install(role, fence_library, usb_library):
global _INSTALLATION
if _INSTALLATION is not None:
if _INSTALLATION.role != role:
raise ValueError("Native guard role cannot change")
return _INSTALLATION
candidate = Protection(role, fence_library, usb_library)
candidate.install_fresh_guard()
_INSTALLATION = candidate
return candidate
@@ -18,10 +18,11 @@ _GUARD_HASHES = {
_POLICY_SHA256 = 'c7eae9e0fab99e8c5fa56854b5a1e1ca8c36c81ef73668dd18ede27de61e3f4b'
_selection = None
_protection = None
_plan = None
def prepare(role):
global _selection
global _selection, _plan
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
@@ -31,9 +32,20 @@ def prepare(role):
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)
if role not in ('driving', 'monitoring'):
raise ValueError('Unknown model runtime role')
if role == 'driving':
import os
from openpilot.starpilot.models.manager import freeze_runtime
_plan = freeze_runtime(recovery_small_only=os.getenv('STARPILOT_MODELD_RECOVERY_SMALL_ONLY') == '1')
if role == 'monitoring' or _plan.family == 'native02e':
from openpilot.starpilot.models.runtime_bridge import native
_selection = native.prepare(role, _plan)
else:
_verify_policy()
cache = Path('/data/starpilot-model-runtime')
_selection = activate(BUNDLE, MANIFEST_SHA256, LEGACY_REVISION, role, cache)
_selection['family'] = 'legacy9d'
return _selection
@@ -46,6 +58,7 @@ def prepare_compiler():
selection = activate(BUNDLE, MANIFEST_SHA256, LEGACY_REVISION, 'driving',
Path(tempfile.gettempdir()).resolve() / 'starpilot-model-compiler')
selection['purpose'] = 'compiler'
selection['family'] = 'legacy9d'
_selection = selection
return _selection
@@ -84,7 +97,15 @@ def _library(name):
def selected_legacy():
return _selection is not None
return _selection is not None and _selection.get('family') == 'legacy9d'
def prepared():
return _selection is not None and _selection.get('purpose') != 'compiler'
def selected_plan():
return _plan
def _guard(chestnut):
@@ -197,6 +218,9 @@ def _load_chestnut(path):
def load_oob(path, chestnut=False):
if _selection is not None and _selection.get('family') == 'native02e':
from openpilot.starpilot.models.runtime_bridge import native
return native.load_oob(path, chestnut)
if _selection is None:
raise RuntimeError('A device model loader requires the selected runtime')
if _selection.get('purpose') == 'compiler':
@@ -232,6 +256,9 @@ def load_oob(path, chestnut=False):
def load_warp(path):
if _selection is not None and _selection.get('family') == 'native02e':
from openpilot.starpilot.models.runtime_bridge import native
return native.load_warp(path)
import pickle
if _selection is None or _selection['role'] != 'monitoring':
raise RuntimeError('A monitoring warp loader requires the selected runtime')
@@ -0,0 +1,54 @@
"""Freeze one driving runtime before importing either tinygrad family."""
from dataclasses import dataclass
import hashlib
import json
NATIVE_REVISION = "02e698160eed1751738212144a7ea2cfb160b3f1"
LEGACY_REVISION = "9d0446a4ba8a532c8b674fb6ad795af015cd9dcf"
NATIVE_ABI = "tinygrad_single_v2_arena"
LEGACY_ABI = "tinygrad_single_v1_arena"
NATIVE_GENERATION = "native02e-v1"
@dataclass(frozen=True)
class RuntimePlan:
family: str
compiler_revision: str
artifact_abi: str
small_id: str
big_id: str
preferences_json: str
entries_json: str
default_json: str
snapshot_sha256: str
def selection_family(preferences: dict, entries: dict, *, recovery_small_only: bool = False) -> str:
# Entries come from validated catalogs: native rows include qualification identity.
if recovery_small_only:
return "native02e"
small, big = preferences["small"], preferences["big"]
if big and big != "bundled-current":
return "native02e" if entries.get(big, {}).get("_runtime_family") == "native02e" else "legacy9d"
return "native02e" if small == "rdf43" or small not in entries else "legacy9d"
def freeze_plan(preferences: dict, entries: dict, default: dict, *, recovery_small_only: bool = False) -> RuntimePlan:
# Keep unconverted BIG and its fallback on legacy9d in the SAME process.
small, big = preferences["small"], preferences["big"]
family = selection_family(preferences, entries, recovery_small_only=recovery_small_only)
native = family == "native02e"
if default["runtime_family"] != family:
raise ValueError("wrong runtime fallback for frozen selection")
selected_big = big if not recovery_small_only and entries.get(big, {}).get("_runtime_family") == family else ""
parts = [json.dumps(value, sort_keys=True, separators=(",", ":"), allow_nan=False) for value in (preferences, entries, default)]
return RuntimePlan(family, NATIVE_REVISION if native else LEGACY_REVISION, NATIVE_ABI if native else LEGACY_ABI,
"rdf43" if native else small, selected_big, *parts,
hashlib.sha256("\n".join(parts).encode()).hexdigest())
def require_artifact_contract(artifact: dict, plan: RuntimePlan | None) -> None:
revision, abi = (plan.compiler_revision, plan.artifact_abi) if plan is not None else (LEGACY_REVISION, LEGACY_ABI)
if (not isinstance(artifact, dict) or artifact.get("compiler_revision") != revision or
artifact.get("artifact_abi") != abi or artifact.get("format_version") != 1):
raise ValueError("catalog model compiler/ABI mismatch")
@@ -0,0 +1,36 @@
"""Release-shaped metadata fixtures; never qualify or execute fixture model bytes."""
from contextlib import contextmanager, ExitStack
import hashlib
import json
from pathlib import Path
from unittest.mock import patch
from openpilot.common.file_chunker import package_file
from openpilot.starpilot.models import manager
from openpilot.starpilot.models import catalog as catalog_module
from openpilot.starpilot.models.runtime_selection import NATIVE_ABI, NATIVE_GENERATION, NATIVE_REVISION
@contextmanager
def native_fixture(root: Path, data: bytes):
shipped = root / 'shipped'
source = root / 'native-fixture-source.pkl'
source.write_bytes(data)
for relative in ('rdf43_driving_tinygrad.pkl', 'compat9d/rdf43_driving_tinygrad.pkl'):
destination = shipped / relative
if not Path(f'{destination}.chunkmanifest').exists():
package_file(source, destination)
source.unlink()
identity = {'artifact_sha256': hashlib.sha256(data).hexdigest(), 'artifact_size': len(data),
'qualification_sha256': hashlib.sha256(b'metadata fixture, no runtime qualification').hexdigest()}
doc = {'generation': NATIVE_GENERATION, 'compiler_revision': NATIVE_REVISION, 'artifact_abi': NATIVE_ABI,
'models': [{'id': 'rdf43', 'name': 'RDF metadata fixture', 'version': 'v15', 'uses_external_gpu': False,
'artifact_format': NATIVE_ABI, 'artifact_chunk_count': 1, **identity}],
'stock': {name: dict(identity) for name in ('dmonitoring_model_tinygrad.pkl',
'dm_warp_1928x1208_tinygrad.pkl', 'dm_warp_1344x760_tinygrad.pkl')}}
path = root / 'native-fixture-catalog.json'
path.write_text(json.dumps(doc))
with ExitStack() as stack:
for module in (manager, catalog_module):
stack.enter_context(patch.object(module, 'NATIVE_CATALOG_PATH', path))
yield doc
@@ -2,13 +2,33 @@ import hashlib
import json
from pathlib import Path
import unittest
import tempfile
from unittest.mock import patch
from openpilot.starpilot.models import manager
from openpilot.starpilot.models.runtime_selection import NATIVE_ABI
from openpilot.starpilot.models.tests.native_fixture import native_fixture
from openpilot.starpilot.models.catalog import BUNDLED_CURRENT, CATALOG, CATALOG_PATH, DEFAULT_SMALL, resolve_selection
from openpilot.starpilot.models.status import (ModelHealth, ModelLoad, ModelOutput, ModelProcess,
ModelVariant, project_status)
class TestModelCatalogStatus(unittest.TestCase):
def setUp(self):
temporary = self.enterContext(tempfile.TemporaryDirectory())
self.root = Path(temporary)
self.enterContext(patch.object(manager, "SHIPPED_MODELS", self.root / "shipped"))
document = self.enterContext(native_fixture(self.root, b"catalog source identity fixture"))
# Status requests need a qualified published BIG identity, not a pending
# historical catalog row. No artifact is executed or marked installed.
historical = json.loads(CATALOG_PATH.read_text())["models"]
big = next(row for row in historical if row["id"] == "cinquev3")
document["models"].append({"id": big["id"], "name": big["name"], "version": big["version"],
"uses_external_gpu": True, "artifact_sha256": "b" * 64,
"artifact_size": 123, "artifact_chunk_count": 1,
"artifact_format": NATIVE_ABI, "qualification_sha256": "c" * 64})
manager.NATIVE_CATALOG_PATH.write_text(json.dumps(document))
def test_catalog_pins_current_source_and_lists_model_metadata(self):
manifest_ids = [row['id'] for row in json.loads(CATALOG_PATH.read_text())['models']]
self.assertEqual(len(manifest_ids), len(set(manifest_ids)))
@@ -17,7 +37,7 @@ class TestModelCatalogStatus(unittest.TestCase):
self.assertCountEqual(catalog_ids, [BUNDLED_CURRENT, *manifest_ids])
entry = resolve_selection(None)
self.assertEqual(entry.model_id, DEFAULT_SMALL)
source = Path(__file__).resolve().parents[4] / entry.source_path
source = manager.SHIPPED_MODELS / Path(entry.source_path).name
from openpilot.common.file_chunker import get_existing_chunks
digest = hashlib.sha256()
for part in get_existing_chunks(source)[1:]:
@@ -10,6 +10,7 @@ from types import SimpleNamespace
import unittest
from unittest.mock import patch
from openpilot.starpilot.models.tests.native_fixture import native_fixture
from openpilot.starpilot.models.catalog import ARTIFACT_ABI, BUNDLED_CURRENT, CATALOG_PATH, DEFAULT_SMALL
from openpilot.starpilot.models.manager import (
ModelError, ModelManager, atomic_json, catalog, preferences, randomize_next_start, resolve_runtime, validate_manifest,
@@ -17,28 +18,30 @@ from openpilot.starpilot.models.manager import (
def run_download_owner(root, manifest, data, entered, release, result):
class BlockedPart(io.BytesIO):
def read(self, size=-1):
entered.set()
if not release.wait(5):
raise OSError('fixture stalled')
return super().read(size)
with native_fixture(Path(root), data) as native_document:
class BlockedPart(io.BytesIO):
def read(self, size=-1):
entered.set()
if not release.wait(5):
raise OSError('fixture stalled')
return super().read(size)
def opener(url, timeout):
if '/manifests/' in url:
return io.BytesIO(json.dumps(manifest).encode())
if url.endswith('chunk01of02'):
return io.BytesIO(data[:10])
return BlockedPart(data[10:])
def opener(url, timeout):
if '/manifests/' in url:
document = native_document if 'native02e-v1' in url else manifest
return io.BytesIO(json.dumps(document).encode())
if url.endswith('chunk01of02'):
return io.BytesIO(data[:10])
return BlockedPart(data[10:])
manager = ModelManager(root=Path(root), parked=lambda: True, gpu_present=lambda: False, opener=opener)
with patch('openpilot.starpilot.models.manager.shutil.disk_usage', return_value=SimpleNamespace(free=1024**3)):
try:
manager.action('download', {'model': 'gwm8223'})
manager.worker.join(6)
result.put(manager.snapshot())
finally:
manager.close()
manager = ModelManager(root=Path(root), parked=lambda: True, gpu_present=lambda: False, opener=opener)
with patch('openpilot.starpilot.models.manager.shutil.disk_usage', return_value=SimpleNamespace(free=1024**3)):
try:
manager.action('download', {'model': 'gwm8223'})
manager.worker.join(6)
result.put(manager.snapshot())
finally:
manager.close()
class ModelManagerTest(unittest.TestCase):
@@ -63,6 +66,7 @@ class ModelManagerTest(unittest.TestCase):
identity = patch('openpilot.starpilot.models.manager.' + attribute, value)
identity.start()
self.addCleanup(identity.stop)
self.native_manifest = self.enterContext(native_fixture(self.root, self.data))
self.manifest = json.loads(CATALOG_PATH.read_text())
self.row = next(x for x in self.manifest['models'] if x['id'] == 'gwm8223')
self.row.update(artifact_format=ARTIFACT_ABI, artifact_sha256=hashlib.sha256(self.data).hexdigest(),
@@ -74,7 +78,8 @@ class ModelManagerTest(unittest.TestCase):
self.assertTrue(url.startswith('https://huggingface.co/buckets/StarPilot-Driving/StarPilot-Resources/resolve/'))
self.urls.append(url)
if '/manifests/' in url:
return io.BytesIO(json.dumps(self.manifest).encode())
document = self.native_manifest if 'native02e-v1' in url else self.manifest
return io.BytesIO(json.dumps(document).encode())
if url.endswith('chunk01of02'):
return io.BytesIO(self.data[:10])
if url.endswith('chunk02of02'):
@@ -136,6 +141,42 @@ class ModelManagerTest(unittest.TestCase):
assert self.manager.selected_gpu_status() == (False, False)
assert not (self.root / '.download-job.json').exists()
def test_downloaded_native_rdf_respects_frozen_big_runtime_family(self):
from openpilot.starpilot.models.runtime_selection import NATIVE_ABI, NATIVE_GENERATION
# Metadata fixture bytes are never unpickled or submitted to a device.
native_rdf = self.root / NATIVE_GENERATION / 'rdf43/rdf43_driving_tinygrad.pkl'
native_rdf.parent.mkdir(parents=True)
native_rdf.write_bytes(self.data)
legacy_big = next(row for row in self.manifest['models'] if row['id'] == 'cinquev3')
legacy_big.update(artifact_format=ARTIFACT_ABI, artifact_sha256=hashlib.sha256(self.data).hexdigest(),
artifact_size=len(self.data), artifact_chunk_count=0)
atomic_json(self.root / 'catalog.json', self.manifest)
for native in (False, True):
with self.subTest(native_big=native):
document = json.loads(json.dumps(self.native_manifest))
if native:
document['models'].append({**legacy_big, 'artifact_format': NATIVE_ABI,
'qualification_sha256': document['models'][0]['qualification_sha256']})
atomic_json(self.root / 'native-fixture-catalog.json', document)
big_path = self.root / NATIVE_GENERATION / 'cinquev3/cinquev3_driving_tinygrad.pkl' if native else (
self.root / 'cinquev3/cinquev3_driving_tinygrad.pkl')
big_path.parent.mkdir(parents=True, exist_ok=True)
big_path.write_bytes(self.data)
atomic_json(self.root / 'preferences.json', {'small': 'rdf43', 'big': 'cinquev3'})
selection = resolve_runtime(True, root=self.root)
self.assertTrue(selection.allow_big)
self.assertEqual(selection.big_path, big_path)
if native:
self.assertEqual(selection.small_path, native_rdf)
self.assertNotIn('compat9d', selection.fallback_path.parts)
else:
self.assertEqual(selection.small_path, selection.fallback_path)
self.assertIn('compat9d', selection.small_path.parts)
self.assertNotEqual(selection.small_path, native_rdf)
self.assertEqual(selection.small_path.read_bytes(), self.data)
self.assertEqual(preferences(self.root)['big'], 'cinquev3')
def test_chunk_download_verifies_and_selects_next_start(self):
self.download()
self.assertEqual(self.manager.progress, 'Downloaded!')
@@ -144,7 +185,8 @@ class ModelManagerTest(unittest.TestCase):
selected = resolve_runtime(True, root=self.root)
self.assertEqual(selected.small_id, 'gwm8223')
self.assertEqual(selected.small_path.read_bytes(), self.data)
self.assertTrue(all('/v26/' in u or 'model_names_v26.json' in u for u in self.urls))
self.assertTrue(all('/v26/' in u or u.endswith(('/manifests/model_names_v26.json',
'/manifests/model_names_native02e-v1.json')) for u in self.urls))
def test_corruption_never_publishes_or_selects(self):
self.bad = True
@@ -654,7 +696,7 @@ class ModelManagerTest(unittest.TestCase):
validate_manifest({**good, "models": [known]})
def test_remote_metadata_cache_refresh_and_root_isolation(self):
from openpilot.starpilot.models.catalog import BY_ID, model_entries
from openpilot.starpilot.models.catalog import model_entries
row = dict(self.row, id="remote_cache", name="First name", version="v16")
atomic_json(self.root / "catalog.json", {**self.manifest, "models": [row]})
self.assertEqual(model_entries(self.root)[row["id"]].name, "First name")
@@ -664,7 +706,7 @@ class ModelManagerTest(unittest.TestCase):
self.assertEqual(model_entries(self.root)[row["id"]].name, "Second name")
with tempfile.TemporaryDirectory() as empty:
self.assertNotIn(row["id"], model_entries(Path(empty)))
self.assertEqual(model_entries(self.root)[DEFAULT_SMALL].source_sha256, BY_ID[DEFAULT_SMALL].source_sha256)
self.assertEqual(model_entries(self.root)[DEFAULT_SMALL].source_sha256, self.native_manifest["models"][0]["artifact_sha256"])
def test_catalog_page_refresh_is_async_bounded_and_parked_only(self):
entered, release = threading.Event(), threading.Event()
@@ -115,6 +115,7 @@ def test_remote_inference_failure_does_not_change_chestnut_status():
def execute(statements, env):
env.setdefault("os", SimpleNamespace(getenv=lambda key: None))
env.setdefault("recovery_small_only", False)
env.setdefault("MODEL_RUNTIME_PLAN", object())
from openpilot.starpilot.models.recovery import STARTUP_FALLBACK_EXIT
env.setdefault("STARTUP_FALLBACK_EXIT", STARTUP_FALLBACK_EXIT)
exec(compile(ast.fix_missing_locations(ast.Module(body=statements, type_ignores=[])), str(MODEL_SOURCE), "exec"), env)
@@ -136,13 +137,16 @@ def test_observed_availability_and_big_eligibility(monkeypatch, present, cable,
import sys
manager = ModuleType("openpilot.starpilot.models.manager")
manager.resolve_runtime = Mock(side_effect=lambda chestnut_available, randomize:
manager.resolve_runtime = Mock(side_effect=lambda chestnut_available, plan:
SimpleNamespace(allow_big=allow_big and chestnut_available, big_path=big_path))
manager.requested_runtime_id = Mock(return_value="selected")
amd = ModuleType("tinygrad.runtime.ops_amd")
amd.AMDDevice = type("FakeAMDDevice", (), {"wait_timeout_ms": 123})
monkeypatch.setitem(sys.modules, manager.__name__, manager)
monkeypatch.setitem(sys.modules, amd.__name__, amd)
am = ModuleType("tinygrad.runtime.support.am")
am.startup_trace = SimpleNamespace(ENABLED=False, REPORT=None)
monkeypatch.setitem(sys.modules, am.__name__, am)
body = main_body()
start = next(i for i, node in enumerate(body) if isinstance(node, ast.ImportFrom) and node.module == manager.__name__)
end = next(i for i, node in enumerate(body) if ast.unparse(node) == "gc.disable()")
@@ -152,8 +156,8 @@ def test_observed_availability_and_big_eligibility(monkeypatch, present, cable,
"jetlink_adapter": SimpleNamespace(link_on=lambda: link)}
execute(body[start:end], env)
available = present or (cable and not link)
manager.resolve_runtime.assert_called_once_with(chestnut_available=available, randomize=True)
manager.requested_runtime_id.assert_called_once_with(available)
manager.resolve_runtime.assert_called_once_with(chestnut_available=available, plan=env["MODEL_RUNTIME_PLAN"])
manager.requested_runtime_id.assert_called_once_with(available, plan=env["MODEL_RUNTIME_PLAN"])
env["chestnut_present"].assert_called_once_with()
assert env["cable_connected"].call_count == (0 if present else 1)
assert env["CHESTNUT"] is expected
@@ -209,6 +213,9 @@ def test_big_worker_waits_before_load_and_failure_requests_fresh_small_process(c
small_version=1, small_id="small", small_sha256="small-hash",
big_version=1, big_id="big", big_sha256="hash")
from openpilot.starpilot.models.catalog import BY_ID, DEFAULT_SMALL, DEFAULT_SMALL_SHA256
selection.fallback_path = "bundled-small"
selection.fallback_version = BY_ID[DEFAULT_SMALL].version
selection.fallback_sha256 = DEFAULT_SMALL_SHA256
env: dict = {"BY_ID": BY_ID, "DEFAULT_SMALL": DEFAULT_SMALL, "DEFAULT_SMALL_SHA256": DEFAULT_SMALL_SHA256,
"time": SimpleNamespace(monotonic=lambda: 0), "cloudlog": Mock(), "selection": selection,
"CHESTNUT": True, "vipc_client_main": SimpleNamespace(width=1928, height=1208), "wait_for_chestnut": wait,
@@ -332,8 +339,8 @@ def test_recovery_child_resolves_small_without_randomizer_or_amd(monkeypatch):
"chestnut_compiled": Mock(side_effect=AssertionError("Big compiled probe forbidden")),
"Params": lambda: params, "os": SimpleNamespace(getenv=lambda key: "1")}
execute(body[start:end], env)
manager.resolve_runtime.assert_called_once_with(chestnut_available=False, randomize=False)
manager.requested_runtime_id.assert_called_once_with(True)
manager.resolve_runtime.assert_called_once_with(chestnut_available=False, plan=env["MODEL_RUNTIME_PLAN"])
manager.requested_runtime_id.assert_called_once_with(True, plan=env["MODEL_RUNTIME_PLAN"])
assert env["CHESTNUT"] is False
assert "AMDDevice" not in env
assert params.put_bool.call_args_list == [(("ChestnutLoading", False),)]
@@ -362,7 +369,9 @@ def test_failed_selected_small_uses_only_verified_rdf_or_refuses_startup(monkeyp
monkeypatch.setattr(manager, 'shipped_default', lambda: None if fallback_failure == 'missing' else shipped)
loader = Mock(side_effect=[RuntimeError('selected failed'), RuntimeError('RDF failed') if fallback_failure == 'load' else (rdf, 'rdf-prepared')])
stock = Mock(side_effect=AssertionError('stock model must not load'))
env: dict = {'selection': SimpleNamespace(small_path=Path('custom.pkl'), small_version='v15', small_id='gwm8223', small_sha256='custom'),
env: dict = {'selection': SimpleNamespace(small_path=Path('custom.pkl'), small_version='v15', small_id='gwm8223', small_sha256='custom',
fallback_path=None if fallback_failure == 'missing' else shipped,
fallback_version='v15', fallback_sha256=DEFAULT_SMALL_SHA256),
'vipc_client_main': SimpleNamespace(width=1928, height=1208), 'load_verified_model': loader,
'ModelState': stock, 'modeld_pkl_path': stock, 'cloudlog': Mock(), 'BY_ID': BY_ID,
'DEFAULT_SMALL': DEFAULT_SMALL, 'DEFAULT_SMALL_SHA256': DEFAULT_SMALL_SHA256}
@@ -0,0 +1,297 @@
"""Metadata-only tests of actual selection functions; no runtime admission proof."""
import ast
from dataclasses import dataclass, FrozenInstanceError, replace
import hashlib
import json
import os
from pathlib import Path
import re
import types
import unittest
from unittest.mock import Mock, patch
SOURCE = Path(os.environ.get("STARPILOT_PARTIAL_NATIVE_SOURCE_ROOT", Path(__file__).resolve().parents[4]))
MODELS = SOURCE / "openpilot/starpilot/models"
def extract(path, names, namespace):
tree = ast.parse(path.read_text(), filename=str(path))
selected = [node for node in tree.body if isinstance(node, (ast.FunctionDef, ast.ClassDef)) and node.name in names]
if {node.name for node in selected} != set(names):
raise AssertionError("Missing actual source definitions")
# Compile the original nodes unchanged, including decorators and annotations.
exec(compile(ast.Module(body=selected, type_ignores=[]), str(path), "exec"), namespace)
def subjects():
ns = {"__name__": __name__, "dataclass": dataclass, "hashlib": hashlib, "json": json, "Path": Path, "re": re}
# This entire module has only standard-library imports; no production imports.
exec(compile((MODELS / "runtime_selection.py").read_text(), str(MODELS / "runtime_selection.py"), "exec"), ns)
ns.update(ROOT=Path("/metadata-fixture"), DEFAULT_SMALL="rdf43", BUNDLED_CURRENT="bundled-current",
GENERATION="v26", COMPILER_REVISION=ns["LEGACY_REVISION"], ARTIFACT_ABI=ns["LEGACY_ABI"],
MODEL_ID=re.compile(r"[a-z0-9][a-z0-9_-]{0,63}\Z"), SHA=re.compile(r"[0-9a-f]{64}\Z"),
SUPPORTED_VERSIONS={f"v{i}" for i in range(8, 17)}, MAX_ARTIFACT=2 * 1024**3,
MODEL_RUNNER_REVISION=2, BY_ID={},
RUNTIME_ARTIFACT_KEYS={"min_runner_revision", "artifact_format", "artifact_sha256", "artifact_size",
"artifact_chunk_count", "conversion_provenance", "validation"},
CATALOG_PATH=Path("/metadata-fixture/legacy.json"), NATIVE_CATALOG_PATH=Path("/metadata-fixture/native.json"))
extract(MODELS / "manager.py", ["ModelError", "validate_manifest", "native_stock", "catalog", "RuntimeSelection",
"requested_runtime_id", "resolve_runtime"], ns)
ns["ModelEntry"] = types.SimpleNamespace
extract(MODELS / "catalog.py", ["resolve_selection"], ns)
return ns
class TestPartialNativeSelection(unittest.TestCase):
def setUp(self):
self.n = subjects()
self.prefs = {"small": "optional-small", "big": "traintrack", "userFavorites": ["optional-small"],
"blacklistedModels": ["another-small"], "randomizer": False, "sortMode": "favorites"}
self.entries = {
"optional-small": {"id": "optional-small", "version": "v15", "_runtime_family": "legacy9d", "artifact_sha256": "a" * 64},
"traintrack": {"id": "traintrack", "version": "v16", "_runtime_family": "native02e", "artifact_sha256": "b" * 64},
}
self.default = {"runtime_family": "native02e", "version": "v15", "artifact_sha256": "c" * 64}
def plan(self, **kw):
return self.n["freeze_plan"](self.prefs, self.entries, self.default, **kw)
def resolve(self, plan, available=True, installed=True, fallback=True):
self.n["verified_artifact"] = Mock(side_effect=lambda mid, *_: Path("/verified") / mid if installed else None)
self.n["shipped_default"] = Mock(return_value=Path("/verified/rdf43") if fallback else None)
return self.n["resolve_runtime"](available, plan=plan)
def document(self, native=True):
return {"generation": self.n["NATIVE_GENERATION"] if native else "v26",
"compiler_revision": self.n["NATIVE_REVISION"] if native else self.n["LEGACY_REVISION"],
"artifact_abi": self.n["NATIVE_ABI"] if native else self.n["LEGACY_ABI"],
"models": [{"id": "traintrack", "name": "TrainTrack", "version": "v16", "uses_external_gpu": True,
"artifact_sha256": "b" * 64, "artifact_size": 123, "artifact_chunk_count": 0,
"artifact_format": self.n["NATIVE_ABI"] if native else self.n["LEGACY_ABI"],
"qualification_sha256": "d" * 64}]}
def test_native_big_uses_native_rdf_without_rewriting_saved_small(self):
before = json.dumps(self.prefs, sort_keys=True)
plan = self.plan()
self.assertEqual((plan.family, plan.small_id, plan.big_id), ("native02e", "rdf43", "traintrack"))
selected = self.resolve(plan)
self.assertTrue(selected.allow_big)
self.assertEqual((selected.small_path, selected.big_path, selected.fallback_path),
(Path("/verified/rdf43"), Path("/verified/traintrack"), Path("/verified/rdf43")))
self.assertEqual(json.dumps(self.prefs, sort_keys=True), before)
self.assertEqual(json.loads(plan.preferences_json), self.prefs)
def test_default_rdf_selects_native_without_a_big_request(self):
self.prefs.update(small="rdf43", big="")
plan = self.plan()
self.assertEqual((plan.family, plan.small_id, plan.big_id), ("native02e", "rdf43", ""))
selected = self.resolve(plan)
self.assertEqual(selected.small_sha256, "c" * 64)
self.assertFalse(selected.allow_big)
def test_unconverted_big_keeps_legacy_payload_custom_small_and_saved_request(self):
self.entries["traintrack"]["_runtime_family"] = "legacy9d"
self.default["runtime_family"] = "legacy9d"
plan = self.plan()
self.assertEqual((plan.family, plan.small_id, plan.big_id), ("legacy9d", "optional-small", "traintrack"))
selected = self.resolve(plan)
self.assertTrue(selected.allow_big)
self.assertEqual((selected.small_id, selected.big_id), ("optional-small", "traintrack"))
self.assertEqual(self.n["requested_runtime_id"](True, plan=plan), "traintrack")
self.assertEqual(self.prefs["big"], "traintrack")
def test_optional_small_only_uses_legacy_and_legacy_fallback(self):
self.prefs["big"] = ""
self.default["runtime_family"] = "legacy9d"
plan = self.plan()
selected = self.resolve(plan)
self.assertEqual((plan.family, plan.compiler_revision, plan.artifact_abi),
("legacy9d", self.n["LEGACY_REVISION"], self.n["LEGACY_ABI"]))
self.assertEqual(selected.small_id, "optional-small")
self.assertEqual(selected.small_sha256, "a" * 64)
self.assertFalse(selected.allow_big)
missing = self.resolve(plan, installed=False)
self.assertEqual((missing.small_id, missing.small_path), ("rdf43", Path("/verified/rdf43")))
def test_absent_chestnut_or_artifact_retains_big_request_but_disables_execution(self):
for available, installed in ((False, True), (True, False)):
with self.subTest(available=available, installed=installed):
selected = self.resolve(self.plan(), available, installed)
self.assertEqual(selected.big_id, "traintrack")
self.assertIsNone(selected.big_path)
self.assertFalse(selected.allow_big)
self.assertEqual(selected.small_id, "rdf43")
def test_recovery_forces_native_rdf_and_preserves_preferences(self):
before = json.dumps(self.prefs, sort_keys=True)
plan = self.plan(recovery_small_only=True)
self.assertEqual((plan.family, plan.small_id, plan.big_id), ("native02e", "rdf43", ""))
self.assertFalse(self.resolve(plan).allow_big)
self.assertEqual(json.dumps(self.prefs, sort_keys=True), before)
def test_frozen_benchmark_snapshot_is_detached_and_does_not_write_preferences(self):
before = json.dumps((self.prefs, self.entries, self.default), sort_keys=True)
first = self.plan()
self.assertEqual(first, self.plan())
self.assertEqual(json.dumps((self.prefs, self.entries, self.default), sort_keys=True), before)
self.assertEqual(first.snapshot_sha256, hashlib.sha256("\n".join(
(first.preferences_json, first.entries_json, first.default_json)).encode()).hexdigest())
self.prefs["small"] = "later-choice"
self.entries["traintrack"]["version"] = "v15"
self.assertEqual(self.n["requested_runtime_id"](True, plan=first), "traintrack")
self.assertEqual(json.loads(first.entries_json)["traintrack"]["version"], "v16")
self.assertNotEqual(first.snapshot_sha256, self.plan().snapshot_sha256)
with self.assertRaises(FrozenInstanceError):
first.small_id = "mutated"
def test_wrong_fallback_and_missing_verified_default_fail_closed(self):
self.default["runtime_family"] = "legacy9d"
with self.assertRaisesRegex(ValueError, "wrong runtime fallback"):
self.plan()
self.default["runtime_family"] = "native02e"
with self.assertRaisesRegex(self.n["ModelError"], "missing or corrupt"):
self.resolve(self.plan(), fallback=False)
def test_unknown_or_pending_selector_is_rejected_without_silent_default(self):
rdf = types.SimpleNamespace(selectable=True)
self.n["model_entries"] = lambda _: {"rdf43": rdf, "pending-big": types.SimpleNamespace(selectable=False)}
for requested in ("unknown", "pending-big", 7):
with self.subTest(requested=requested), self.assertRaisesRegex(ValueError, "unqualified"):
self.n["resolve_selection"](requested)
self.assertIs(self.n["resolve_selection"](None), rdf)
self.assertIs(self.n["resolve_selection"]("bundled-current"), rdf)
def test_artifact_revision_abi_and_format_are_exact(self):
for plan in (self.plan(), None):
artifact = {"compiler_revision": plan.compiler_revision if plan else self.n["LEGACY_REVISION"],
"artifact_abi": plan.artifact_abi if plan else self.n["LEGACY_ABI"], "format_version": 1}
self.n["require_artifact_contract"](artifact, plan)
for key, bad in (("compiler_revision", "0" * 40), ("compiler_revision", "corrupt"),
("artifact_abi", "unknown"), ("format_version", 2)):
with self.subTest(key=key, bad=bad), self.assertRaisesRegex(ValueError, "compiler/ABI mismatch"):
self.n["require_artifact_contract"]({**artifact, key: bad}, plan)
def test_native_manifest_requires_exact_revision_abi_and_qualification(self):
good = self.document()
row = self.n["validate_manifest"](good, native=True)["traintrack"]
self.assertEqual((row["_runtime_family"], row["compiler_revision"]), ("native02e", self.n["NATIVE_REVISION"]))
for key, bad in (("compiler_revision", self.n["LEGACY_REVISION"]), ("compiler_revision", "corrupt"),
("artifact_abi", self.n["LEGACY_ABI"]), ("generation", "unknown")):
with self.subTest(key=key), self.assertRaises(self.n["ModelError"]):
self.n["validate_manifest"]({**good, key: bad}, native=True)
for key, bad in (("qualification_sha256", "bad"), ("uses_external_gpu", False), ("artifact_format", "unknown")):
doc = self.document()
doc["models"][0][key] = bad
with self.subTest(key=key), self.assertRaises(self.n["ModelError"]):
self.n["validate_manifest"](doc, native=True)
def test_catalog_preserves_unconverted_legacy_big_payload(self):
doc = self.document(native=False)
small = {**doc["models"][0], "id": "optional-small", "uses_external_gpu": False}
doc["models"].append(small)
self.n["read_json"] = Mock(side_effect=[doc, FileNotFoundError()])
self.n["native_catalog"] = Mock(return_value={})
rows = self.n["catalog"]()
self.assertEqual(set(rows), {"traintrack", "optional-small"})
self.assertEqual(rows["traintrack"]["_runtime_family"], "legacy9d")
self.assertEqual(rows["traintrack"]["artifact_sha256"], "b" * 64)
self.assertEqual(rows["optional-small"]["_runtime_family"], "legacy9d")
self.assertEqual(rows["optional-small"]["artifact_sha256"], "b" * 64)
def test_legacy_big_forces_legacy_even_with_saved_default_rdf(self):
self.prefs["small"] = "rdf43"
self.entries["traintrack"]["_runtime_family"] = "legacy9d"
self.default["runtime_family"] = "legacy9d"
plan = self.plan()
selected = self.resolve(plan)
self.assertEqual((plan.family, plan.small_id, plan.big_id), ("legacy9d", "rdf43", "traintrack"))
self.assertTrue(selected.allow_big)
self.assertEqual(selected.small_sha256, "c" * 64)
def test_native_counterpart_switches_new_snapshot_only(self):
self.entries["traintrack"]["_runtime_family"] = "legacy9d"
self.default["runtime_family"] = "legacy9d"
old = self.plan()
self.entries["traintrack"]["_runtime_family"] = "native02e"
self.default["runtime_family"] = "native02e"
new = self.plan()
self.assertEqual((old.family, new.family), ("legacy9d", "native02e"))
self.assertEqual((old.small_id, new.small_id), ("optional-small", "rdf43"))
self.assertTrue(self.resolve(old).allow_big)
self.assertTrue(self.resolve(new).allow_big)
self.assertEqual(self.prefs["small"], "optional-small")
def test_native_overlay_replaces_only_converted_big_id(self):
doc = self.document(native=False)
doc["models"].append({**doc["models"][0], "id": "unconverted-big"})
native = self.n["validate_manifest"](self.document(), native=True)
self.n["read_json"] = Mock(side_effect=[doc, FileNotFoundError()])
self.n["native_catalog"] = Mock(return_value=native)
rows = self.n["catalog"]()
self.assertEqual(set(rows), {"traintrack", "unconverted-big"})
self.assertEqual(rows["traintrack"]["_runtime_family"], "native02e")
self.assertEqual(rows["unconverted-big"]["_runtime_family"], "legacy9d")
self.assertEqual(rows["unconverted-big"]["artifact_sha256"], "b" * 64)
def test_legacy_big_missing_hardware_or_payload_uses_same_family_fallback(self):
self.entries["traintrack"]["_runtime_family"] = "legacy9d"
self.default["runtime_family"] = "legacy9d"
plan = self.plan()
for available, installed in ((False, True), (True, False)):
selected = self.resolve(plan, available=available, installed=installed)
self.assertFalse(selected.allow_big)
self.assertEqual(selected.big_id, "traintrack")
self.assertEqual(selected.fallback_sha256, "c" * 64)
self.assertEqual(plan.family, "legacy9d")
def test_bundled_big_sentinel_does_not_force_a_legacy_session(self):
self.prefs.update(small="rdf43", big="bundled-current")
plan = self.plan()
self.assertEqual((plan.family, plan.small_id, plan.big_id), ("native02e", "rdf43", ""))
def test_cross_family_big_frozen_row_is_rejected(self):
plan = self.plan()
entries = json.loads(plan.entries_json)
entries["traintrack"]["_runtime_family"] = "legacy9d"
with self.assertRaisesRegex(self.n["ModelError"], "Big artifact does not match"):
self.resolve(replace(plan, entries_json=json.dumps(entries)))
def test_native_monitoring_stock_is_separate_complete_release(self):
doc = self.document()
names = {"dmonitoring_model_tinygrad.pkl", "dm_warp_1928x1208_tinygrad.pkl", "dm_warp_1344x760_tinygrad.pkl"}
doc["stock"] = {name: {"artifact_sha256": "e" * 64, "artifact_size": 23, "qualification_sha256": "f" * 64}
for name in names}
self.n["read_json"] = lambda _: doc
self.assertEqual(set(self.n["native_stock"]()), names)
del doc["stock"]["dm_warp_1344x760_tinygrad.pkl"]
with self.assertRaisesRegex(self.n["ModelError"], "not qualified"):
self.n["native_stock"]()
def test_monitoring_prepare_never_freezes_driving_preferences(self):
ns = {"__name__": __name__, "_selection": None, "_plan": None}
extract(MODELS / "runtime_bridge/runtime.py", ["prepare"], ns)
hardware = types.ModuleType("openpilot.common.hardware")
hardware.AGNOS = True
bridge = types.ModuleType("openpilot.starpilot.models.runtime_bridge")
bridge.native = types.SimpleNamespace(prepare=Mock(return_value={"role": "monitoring", "family": "native02e"}))
with patch.dict("sys.modules", {"openpilot.common.hardware": hardware,
"openpilot.starpilot.models.runtime_bridge": bridge}):
selected = ns["prepare"]("monitoring")
bridge.native.prepare.assert_called_once_with("monitoring", None)
self.assertEqual(selected, {"role": "monitoring", "family": "native02e"})
self.assertIsNone(ns["_plan"])
def test_cross_family_small_and_postfreeze_randomization_rejected(self):
self.prefs["big"] = ""
self.default["runtime_family"] = "legacy9d"
plan = self.plan()
bad_entries = json.loads(plan.entries_json)
bad_entries["optional-small"]["_runtime_family"] = "native02e"
with self.assertRaisesRegex(self.n["ModelError"], "does not match"):
self.resolve(replace(plan, entries_json=json.dumps(bad_entries)))
with self.assertRaisesRegex(self.n["ModelError"], "Randomize before freezing"):
self.n["resolve_runtime"](True, plan=plan, randomize=True)
if __name__ == "__main__":
unittest.main()
@@ -1,22 +1,41 @@
import hashlib
import json
import pytest
from openpilot.common.file_chunker import package_file
from openpilot.starpilot.models import manager
from openpilot.starpilot.models.catalog import BUNDLED_CURRENT, DEFAULT_SMALL, DEFAULT_SMALL_SHA256
from openpilot.starpilot.models.catalog import BUNDLED_CURRENT, DEFAULT_SMALL
from openpilot.starpilot.models.runtime_selection import freeze_plan, selection_family
from openpilot.starpilot.models.tests.native_fixture import native_fixture
@pytest.fixture(autouse=True)
def release_metadata(tmp_path, monkeypatch):
data = b"verified fixture"
monkeypatch.setattr(manager, "SHIPPED_MODELS", tmp_path / "shipped")
monkeypatch.setattr(manager, "DEFAULT_SMALL_SHA256", hashlib.sha256(data).hexdigest())
monkeypatch.setattr(manager, "DEFAULT_SMALL_SIZE", len(data))
with native_fixture(tmp_path, data):
yield
def frozen_plan(root):
prefs, entries = manager.preferences(root), manager.catalog(root)
family = selection_family(prefs, entries)
return freeze_plan(prefs, entries, manager.default_record(family, root))
def test_unset_small_loads_shipped_rdf_v4_with_v15_parser(tmp_path, monkeypatch):
artifact = tmp_path / "shipped.pkl"
artifact.write_bytes(b"verified fixture")
monkeypatch.setattr(manager, "shipped_default", lambda: artifact)
selected = manager.resolve_runtime(False, root=tmp_path)
monkeypatch.setattr(manager, "shipped_default", lambda plan=None: artifact)
selected = manager.resolve_runtime(False, root=tmp_path, plan=frozen_plan(tmp_path))
assert manager.preferences(tmp_path)["small"] == DEFAULT_SMALL
assert selected.small_id == "rdf43"
assert selected.small_version == "v15"
assert selected.small_path == artifact
assert selected.small_sha256 == DEFAULT_SMALL_SHA256
assert selected.small_sha256 == manager.default_record("native02e", tmp_path)["artifact_sha256"]
assert not selected.allow_big
@@ -24,16 +43,16 @@ def test_unset_small_loads_shipped_rdf_v4_with_v15_parser(tmp_path, monkeypatch)
def test_legacy_alias_and_missing_selected_artifact_use_rdf_only(tmp_path, monkeypatch, selection):
manager.atomic_json(tmp_path / "preferences.json", {"small": selection})
path = tmp_path / "rdf.pkl"
monkeypatch.setattr(manager, "shipped_default", lambda: path)
selected = manager.resolve_runtime(False, root=tmp_path)
monkeypatch.setattr(manager, "shipped_default", lambda plan=None: path)
selected = manager.resolve_runtime(False, root=tmp_path, plan=frozen_plan(tmp_path))
assert selected.small_id == DEFAULT_SMALL and selected.small_path == path
assert manager.preferences(tmp_path)["small"] == (DEFAULT_SMALL if selection == BUNDLED_CURRENT else selection)
def test_missing_or_corrupt_only_shipped_model_refuses_startup(tmp_path, monkeypatch):
monkeypatch.setattr(manager, "shipped_default", lambda: None)
monkeypatch.setattr(manager, "shipped_default", lambda plan=None: None)
with pytest.raises(manager.ModelError, match="Shipped RDFv4 is missing or corrupt"):
manager.resolve_runtime(False, root=tmp_path)
manager.resolve_runtime(False, root=tmp_path, plan=frozen_plan(tmp_path))
def test_big_default_disabled_and_explicit_download_preserved(tmp_path):
@@ -48,30 +67,42 @@ def test_verified_download_precedes_shipped_default(tmp_path, monkeypatch):
path = tmp_path / "download.pkl"
monkeypatch.setattr(manager, "catalog", lambda _: {DEFAULT_SMALL: {"artifact_sha256": "download-hash", "version": "v15"}})
monkeypatch.setattr(manager, "verified_artifact", lambda *args: path)
def unexpected_shipped_default():
raise AssertionError("download replaced")
monkeypatch.setattr(manager, "shipped_default", unexpected_shipped_default)
selected = manager.resolve_runtime(False, root=tmp_path)
fallback = tmp_path / "fallback.pkl"
def verified_fallback(plan):
assert plan is not None
return fallback
monkeypatch.setattr(manager, "shipped_default", verified_fallback)
selected = manager.resolve_runtime(False, root=tmp_path, plan=frozen_plan(tmp_path))
assert selected.small_path == path
assert selected.small_sha256 == "download-hash"
assert selected.fallback_path == fallback
assert selected.small_path != selected.fallback_path
@pytest.mark.parametrize("family", ["native02e", "legacy9d"])
@pytest.mark.parametrize("damage", [None, "bytes", "size", "missing"])
def test_shipped_parts_require_pinned_hash_and_size(tmp_path, monkeypatch, damage):
def test_shipped_parts_require_pinned_hash_and_size(tmp_path, monkeypatch, damage, family):
data = b"small isolated compiled artifact fixture"
source = tmp_path / "source.pkl"
source.write_bytes(data)
destination = tmp_path / "rdf43_driving_tinygrad.pkl"
destination = tmp_path / ("compat9d/rdf43_driving_tinygrad.pkl" if family == "legacy9d" else "rdf43_driving_tinygrad.pkl")
package_file(source, destination)
monkeypatch.setattr(manager, "SHIPPED_MODELS", tmp_path)
monkeypatch.setattr(manager, "DEFAULT_SMALL_SHA256", hashlib.sha256(data).hexdigest())
monkeypatch.setattr(manager, "DEFAULT_SMALL_SIZE", len(data) + (damage == "size"))
part = tmp_path / "rdf43_driving_tinygrad.pkl.chunk01of01"
with native_fixture(tmp_path, data) as document:
if damage == "size":
document["models"][0]["artifact_size"] += 1
manager.NATIVE_CATALOG_PATH.write_text(json.dumps(document))
record = manager.default_record(family, tmp_path)
plan = freeze_plan({"small": "rdf43" if family == "native02e" else "gwm8223", "big": ""},
{"gwm8223": {"_runtime_family": "legacy9d"}}, record)
part = destination.with_name(destination.name + ".chunk01of01")
if damage == "bytes":
part.write_bytes(b"x" * len(data))
elif damage == "missing":
part.unlink()
result = manager.shipped_default()
result = manager.shipped_default(plan)
assert (result is not None) == (damage is None)
if result is not None:
assert result.read_bytes() == data
@@ -86,15 +117,16 @@ def test_shipped_default_is_verified_asynchronously_in_menu(tmp_path, monkeypatc
monkeypatch.setattr(manager, "SHIPPED_MODELS", shipped)
monkeypatch.setattr(manager, "DEFAULT_SMALL_SHA256", hashlib.sha256(data).hexdigest())
monkeypatch.setattr(manager, "DEFAULT_SMALL_SIZE", len(data))
owner = manager.ModelManager(root=tmp_path / "preferences", parked=lambda: True, gpu_present=lambda: False)
try:
owner.snapshot()
if owner.verify_worker is not None:
owner.verify_worker.join(2)
row = next(row for row in owner.snapshot()["models"] if row["value"] == DEFAULT_SMALL)
assert row["installed"] and row["selectable"] and not row["checking"]
owner.action("active", {"profile": "small", "model": DEFAULT_SMALL})
selected = manager.resolve_runtime(False, root=owner.root)
assert selected.small_id == DEFAULT_SMALL and selected.small_path.read_bytes() == data
finally:
owner.close()
with native_fixture(tmp_path, data):
owner = manager.ModelManager(root=tmp_path / "preferences", parked=lambda: True, gpu_present=lambda: False)
try:
owner.snapshot()
if owner.verify_worker is not None:
owner.verify_worker.join(2)
row = next(row for row in owner.snapshot()["models"] if row["value"] == DEFAULT_SMALL)
assert row["installed"] and row["selectable"] and not row["checking"]
owner.action("active", {"profile": "small", "model": DEFAULT_SMALL})
selected = manager.resolve_runtime(False, root=owner.root, plan=frozen_plan(owner.root))
assert selected.small_id == DEFAULT_SMALL and selected.small_path.read_bytes() == data
finally:
owner.close()
+1 -1
View File
@@ -1 +1 @@
{"protected_inventory_sha256": "3f83520d0fdaa65f801f3314d8b6e41714090eac5fb8fcd3ed70df2e0dc539b0", "schema_version": 1}
{"protected_inventory_sha256": "513a506b42cd5503d092aa0ae67f235b9db263f46127f1a934dff5af710c5484", "schema_version": 1}
+29 -6
View File
@@ -57,7 +57,7 @@ def sha256_file(path: Path) -> str:
return digest.hexdigest()
def compile_env(gpu: bool = False) -> dict[str, str]:
def compile_env(gpu: bool = False, *, native: bool = False) -> dict[str, str]:
env = dict(os.environ)
env["PYTHONPATH"] = os.pathsep.join((str(REPO_ROOT), str(REPO_ROOT / "tinygrad_repo"), env.get("PYTHONPATH", "")))
defaults = {"DEV": "QCOM", "IMAGE": "1", "FLOAT16": "1", "NOLOCALS": "1", "JIT_BATCH_SIZE": "0", "OPENPILOT_HACKS": "1"}
@@ -67,6 +67,8 @@ def compile_env(gpu: bool = False) -> dict[str, str]:
for key in ("IMAGE", "NOLOCALS", "OPENPILOT_HACKS"):
env.pop(key, None)
env.update(DEV="USB+AMD:LLVM", WARP_DEV="QCOM", GMMU="0", TC_OPT="2", TC_MIN_GLOBALS="32")
if native:
env["BEAM"] = "0"
return env
@@ -92,12 +94,17 @@ def parse_args(argv=None):
return args
def bank_compiler_revision() -> str:
def bank_compiler_revision(*, native: bool = False) -> str:
# The global runtime can advance while the compiled bank keeps its own ABI.
old_path = sys.path[:]
try:
sys.path.insert(0, str(REPO_ROOT))
from openpilot.starpilot.models import catalog
if native:
from openpilot.starpilot.models import runtime_selection
if Path(runtime_selection.__file__).resolve() != (REPO_ROOT / "openpilot/starpilot/models/runtime_selection.py").resolve():
raise ValueError("Native compiler identity came from a different checkout")
return runtime_selection.NATIVE_REVISION
from openpilot.starpilot.models.runtime_bridge import bootstrap, runtime
for module, relative in ((catalog, "catalog.py"), (bootstrap, "runtime_bridge/bootstrap.py"),
(runtime, "runtime_bridge/runtime.py")):
@@ -126,11 +133,13 @@ def main(argv=None):
output_dir = args.output_dir.resolve()
output_dir.mkdir(parents=True, exist_ok=True)
output = output_dir / f"{args.model}_driving_tinygrad.pkl"
env = compile_env(args.gpu)
native = args.gpu or args.model == "rdf43"
env = compile_env(args.gpu, native=native)
with tempfile.TemporaryDirectory(prefix=f".{args.model}-compile-", dir=output_dir) as temporary:
env["TMPDIR"] = temporary
staged = Path(temporary) / output.name
command = [sys.executable, "-m", "openpilot.starpilot.models.compile", "--model-type", model_type,
compiler_module = "openpilot.starpilot.models.compile_native" if native else "openpilot.starpilot.models.compile"
command = [sys.executable, "-m", compiler_module, "--model-type", model_type,
"--model-size", "512x256", "--camera-resolutions", "1928x1208", "1344x760",
"--behavior-version", args.version, "--output", str(staged), "--out-of-band",
"--image-history-pipeline", args.image_history_pipeline, *source_args]
@@ -139,12 +148,26 @@ def main(argv=None):
subprocess.run(command, cwd=REPO_ROOT, env=env, check=True)
if {path.name: sha256_file(path) for path in sources} != before:
raise ValueError("ONNX source changed during compilation")
revision = bank_compiler_revision()
revision = bank_compiler_revision(native=native)
receipt = {"model_id": args.model, "behavior_version": args.version, "model_type": model_type,
"artifact_abi": "tinygrad_single_v1_arena", "compiler_revision": revision,
"artifact_abi": "tinygrad_single_v2_arena" if native else "tinygrad_single_v1_arena", "compiler_revision": revision,
"source_sha256": before, "artifact_sha256": sha256_file(staged), "artifact_size": staged.stat().st_size,
"execution_device": env["DEV"], "warp_device": env.get("WARP_DEV", env["DEV"]),
"image_history_pipeline": args.image_history_pipeline, "camera_resolutions": [[1928, 1208], [1344, 760]]}
if native:
receipt["qualification_status"] = "RAW_UNGUARDED_NOT_PUBLISHABLE"
launch, launch_receipt = staged.with_suffix(".launch.pkl"), staged.with_suffix(".launch.json")
companion = json.loads(launch_receipt.read_text())
if (companion.get("format") != "tinygrad_pre_hcq_v1" or companion.get("compiler_revision") != revision or
companion.get("artifact_abi") != receipt["artifact_abi"] or
companion.get("native_artifact_sha256") != receipt["artifact_sha256"] or
companion.get("launch_sha256") != sha256_file(launch)):
raise ValueError("Native compiler companion identity mismatch")
receipt["launch_sha256"] = companion["launch_sha256"]
# Offline export only; inference never loads the companion. Guard
# preparation and artifact/device qualification remain separate gates.
launch.replace(output.with_suffix(".launch.pkl"))
launch_receipt.replace(output.with_suffix(".launch.json"))
staged.replace(output)
receipt_path = output.with_suffix(".build.json")
temporary_receipt = Path(temporary) / receipt_path.name
@@ -1,6 +1,7 @@
"""Package the compiled small-model outputs using established Dom model parts."""
import argparse
from pathlib import Path
import sys
# Resolve this checkout's stdlib-only owner, even for an absolute CLI invocation
@@ -16,9 +17,16 @@ def main():
parser.add_argument('--source', type=Path, required=True)
parser.add_argument('--destination', type=Path, required=True)
args = parser.parse_args()
root = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(root))
from tools.laptop_device_build.qcom_artifact_contract import native_records, verified_native_asset
native = native_records(root) if args.source.resolve() == (root / 'openpilot/selfdrive/modeld/models').resolve() else None
for name in ('dmonitoring_model_tinygrad.pkl', 'rdf43_driving_tinygrad.pkl'):
source = (_chunk_owner.materialize_file_chunked(args.source / name)
if name == 'rdf43_driving_tinygrad.pkl' else args.source / name)
if native is not None:
source = verified_native_asset(root, name, prefer_regular=True)
else:
source = (_chunk_owner.materialize_file_chunked(args.source / name)
if name == 'rdf43_driving_tinygrad.pkl' or not (args.source / name).is_file() else args.source / name)
package_file(source, args.destination / name)
if __name__ == '__main__':
@@ -1,6 +1,9 @@
"""Exact source and artifact contract for laptop-only DM and warp QCOM imports."""
import argparse
import ast
import importlib
import importlib.util
import hashlib
import json
import os
@@ -68,6 +71,140 @@ def compiler_tree(root: Path) -> Path:
return legacy / 'tinygrad_repo'
NATIVE_ASSETS = ("rdf43_driving_tinygrad.pkl", "dmonitoring_model_tinygrad.pkl",
"dm_warp_1344x760_tinygrad.pkl", "dm_warp_1928x1208_tinygrad.pkl")
NATIVE_BUILD_SOURCES = (
"openpilot/common/transformations/camera.py",
"openpilot/common/transformations/model.py",
"openpilot/system/camerad/cameras/nv12_info.py",
"openpilot/selfdrive/modeld/models/dmonitoring_model.onnx",
"openpilot/starpilot/models/compile_native.py",
)
NATIVE_REVISION = "02e698160eed1751738212144a7ea2cfb160b3f1"
def _logical_source(path: Path) -> dict:
if path.is_symlink() or not path.is_file():
raise ValueError(f"Missing regular native build input: {path}")
with path.open("rb") as stream:
prefix = stream.read(1024)
if prefix.startswith(b"version https://git-lfs.github.com/spec/v1\n"):
match = re.fullmatch(rb"version https://git-lfs.github.com/spec/v1\noid sha256:([0-9a-f]{64})\nsize ([1-9][0-9]*)\n?", prefix)
if match is None or len(prefix) != path.stat().st_size:
raise ValueError(f"Invalid native build LFS pointer: {path}")
return {"sha256": match[1].decode(), "bytes": int(match[2])}
return {"sha256": _sha(path), "bytes": path.stat().st_size}
def _native_dm_geometry(root: Path, width: int, height: int) -> dict:
path = root / "openpilot/system/camerad/cameras/nv12_info.py"
spec = importlib.util.spec_from_file_location("_native_build_nv12", path)
if spec is None or spec.loader is None:
raise ValueError("Missing native NV12 source loader")
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
tree = ast.parse((root / "openpilot/common/transformations/model.py").read_text())
sizes = [ast.literal_eval(node.value) for node in tree.body if isinstance(node, ast.Assign) and
any(isinstance(target, ast.Name) and target.id == "DM_INPUT_SIZE" for target in node.targets)]
if len(sizes) != 1:
raise ValueError("Missing native DM target geometry")
return {"camera": [width, height], "nv12": list(module.get_nv12_info(width, height)),
"warp_to": list(sizes[0]), "layout": "luma", "border_fill": 16, "transform_device": "NPY"}
def native_records(root: Path, *, verify_runtime: bool = False) -> dict | None:
"""Read sealed native defaults separately from the unchanged legacy compiler package."""
root = root.resolve()
bridge = root / "openpilot/starpilot/models/runtime_bridge"
source, catalog = bridge / "native.py", bridge.parent / "catalog-native02e.json"
if not source.exists() and not catalog.exists():
return None
for path in (source, catalog, bridge / "native-policy.json"):
if path.is_symlink() or not path.is_file() or path.stat().st_size > 2 * 1024**2:
raise ValueError("Missing or invalid sealed native build contract")
nodes = ast.parse(source.read_text()).body
pins = [ast.literal_eval(node.value) for node in nodes if isinstance(node, ast.Assign) and
any(isinstance(target, ast.Name) and target.id == "POLICY_SHA256" for target in node.targets)]
if len(pins) != 1 or not _valid_sha(pins[0]) or _sha(bridge / "native-policy.json") != pins[0]:
raise ValueError("Native policy is not release-qualified")
policy = json.loads((bridge / "native-policy.json").read_text(), object_pairs_hook=_unique_pairs)
document = json.loads(catalog.read_text(), object_pairs_hook=_unique_pairs)
if (policy.get("compiler_revision") != NATIVE_REVISION or policy.get("catalog_sha256") != _sha(catalog) or
document.get("compiler_revision") != NATIVE_REVISION or document.get("generation") != "native02e-v1" or
document.get("artifact_abi") != "tinygrad_single_v2_arena"):
raise ValueError("Native build runtime/catalog identity changed")
build_sources = policy.get("build_sources")
if not isinstance(build_sources, dict) or set(build_sources) != set(NATIVE_BUILD_SOURCES):
raise ValueError("Native release lacks exact build source qualification")
for name, expected in build_sources.items():
if _logical_source(root / name) != expected:
raise ValueError("Native build source changed; requalification required: " + name)
rows = document.get("models")
if not isinstance(rows, list):
raise ValueError("Invalid native model catalog")
rdf = [row for row in rows if isinstance(row, dict) and row.get("id") == "rdf43"]
stock = document.get("stock")
if len(rdf) != 1 or not isinstance(stock, dict) or set(stock) != set(NATIVE_ASSETS) - {NATIVE_ASSETS[0]}:
raise ValueError("Native bundled model set changed")
records = {NATIVE_ASSETS[0]: rdf[0], **stock}
for name, row in records.items():
if (not isinstance(row, dict) or not _valid_sha(row.get("artifact_sha256")) or
type(row.get("artifact_size")) is not int or not 0 < row["artifact_size"] <= 2 * 1024**3 or
not _valid_sha(row.get("qualification_sha256"))):
raise ValueError("Invalid qualified native artifact: " + name)
if name in NATIVE_ASSETS[:2]:
if (not _valid_sha(row.get("source_sha256")) or type(row.get("source_bytes")) is not int or
not 0 < row["source_bytes"] <= 2 * 1024**3):
raise ValueError("Missing native ONNX source identity: " + name)
else:
width, height = map(int, name.removeprefix("dm_warp_").removesuffix("_tinygrad.pkl").split("x"))
if row.get("source_geometry") != _native_dm_geometry(root, width, height):
raise ValueError("Native DM warp geometry changed")
dm = records[NATIVE_ASSETS[1]]
if {"sha256": dm["source_sha256"], "bytes": dm["source_bytes"]} != build_sources[NATIVE_BUILD_SOURCES[3]]:
raise ValueError("Native DM ONNX identity changed")
if verify_runtime:
sys.path.insert(0, str(root))
native = importlib.import_module("openpilot.starpilot.models.runtime_bridge.native")
if Path(native.__file__).resolve() != source.resolve() or native.ROOT.resolve() != root:
raise ValueError("Native build runtime import escaped this checkout")
if native._verify_policy() != policy:
raise ValueError("Native runtime/vendor/guard contract changed")
return records
def verified_native_asset(root: Path, name: str, *, prefer_regular: bool = False) -> Path:
"""Authenticate shipped assets; never regenerate them from legacy recipes."""
records = native_records(root)
if records is None or name not in records:
raise ValueError("Unknown native bundled artifact")
from openpilot.common.file_chunker import materialize_file_chunked
row, path = records[name], root / MODEL_DIR / name
if prefer_regular and path.is_file() and not path.is_symlink() and _sha(path) == row["artifact_sha256"]:
result = path
else:
result = materialize_file_chunked(path, expected_sha256=row["artifact_sha256"])
if result.is_symlink() or result.stat().st_size != row["artifact_size"] or _sha(result) != row["artifact_sha256"]:
raise ValueError("Native bundled artifact changed: " + name)
return result
def verify_native_defaults(root: Path, *, prefer_regular: bool = False) -> dict:
records = native_records(root, verify_runtime=True)
if records is None:
raise ValueError("Native bundle is not selected")
for name in NATIVE_ASSETS:
verified_native_asset(root, name, prefer_regular=prefer_regular)
# Optional legacy models need their separately retained legacy RDF fallback.
from openpilot.common.file_chunker import materialize_file_chunked
from openpilot.starpilot.models.catalog import DEFAULT_SMALL_SHA256, DEFAULT_SMALL_SIZE
compat = materialize_file_chunked(root / MODEL_DIR / "compat9d/rdf43_driving_tinygrad.pkl", DEFAULT_SMALL_SHA256)
if compat.is_symlink() or compat.stat().st_size != DEFAULT_SMALL_SIZE:
raise ValueError("Retained legacy RDF fallback changed")
return records
def commands(root: Path) -> dict[str, str]:
sys.path.insert(0, str(root))
from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye
@@ -106,7 +243,8 @@ def _sources(root: Path) -> dict[str, str]:
# Logical compiler paths retain the original package provenance after relocation.
return ('tinygrad_repo/' + path.relative_to(compiler).as_posix() if path.is_relative_to(compiler)
else path.relative_to(root).as_posix())
return {source_name(path): _sha(path) for path in sorted(paths)}
return {source_name(path): (_logical_source(path)["sha256"] if path.suffix == ".onnx" else _sha(path))
for path in sorted(paths)}
def _tokens(command: str, root: Path) -> list[str]:
@@ -209,9 +209,13 @@ def validate(root: Path) -> None:
raise ValueError(f"camera warp set differs from current camera configs: {sorted(actual_warps)}")
for name in (*MODELS, *WARPS):
require_qcom_pickle(model_dir / name)
shipped = materialize_file_chunked(model_dir / f"{DEFAULT_SMALL}_driving_tinygrad.pkl", DEFAULT_SMALL_SHA256)
if shipped.stat().st_size != DEFAULT_SMALL_SIZE:
raise ValueError("Shipped default model size differs from the published artifact")
from tools.laptop_device_build.qcom_artifact_contract import native_records, verify_native_defaults
if native_records(root) is not None:
verify_native_defaults(root)
else:
shipped = materialize_file_chunked(model_dir / f"{DEFAULT_SMALL}_driving_tinygrad.pkl", DEFAULT_SMALL_SHA256)
if shipped.stat().st_size != DEFAULT_SMALL_SIZE:
raise ValueError("Shipped default model size differs from the published artifact")
def main() -> None: