1
0
forked from IQ.Lvbs/IQ.Pilot

IQ.Pilot Release Commit @ 0798119

This commit is contained in:
IQ.Lvbs history cleanup
2026-08-22 23:42:42 -05:00
commit b42569dbca
4529 changed files with 1132125 additions and 0 deletions

1
iqpilot/selfdrive/iqmodeld/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*_pyx.cpp

View File

@@ -0,0 +1,131 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
import glob
import os
Import("env", "envCython", "arch", "cereal", "messaging", "common", "visionipc")
lenv = env.Clone()
lenvCython = envCython.Clone()
libs = [cereal, messaging, visionipc, common, "capnp", "kj", "pthread"]
frameworks = []
core_sources = ["native/iqmodel.cc", "transforms/yuv.cc", "transforms/warp_geometry.cc"]
SMALL_MODEL_NAMES = ["supercombo", "driving_vision", "driving_off_policy", "driving_on_policy", "driving_policy"]
FUSED_TRIPLET = ["driving_vision", "driving_off_policy", "driving_on_policy"]
PC = not os.path.isfile("/TICI")
def _inject_path_define(symbol, filename):
quoted = f'-D{symbol}_PATH=\\"{File(filename).abspath}\\"'
for active_env in (lenv, lenvCython):
active_env["CXXFLAGS"].append(quoted)
def _tinygrad_sources():
root = env.Dir("#tinygrad_repo").relpath
workspace = env.Dir("#").abspath
return ["#" + path for path in glob.glob(root + "/**", recursive=True, root_dir=workspace) if "pycache" not in path]
def _present_models():
return [name for name in SMALL_MODEL_NAMES if File(f"models/{name}.onnx").exists()]
def _tinygrad_flags():
if arch == "larch64":
return "DEV=QCOM IMAGE=2 FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 OPENPILOT_HACKS=1"
if arch == "Darwin":
return f'DEV=CPU HOME={os.path.expanduser("~")} IMAGE=0'
if arch == "x86_64":
return "DEV=CPU:LLVM IMAGE=0"
return "DEV=CPU:LLVM IMAGE=0"
def _fused_flags():
if arch == "larch64":
return "DEV=QCOM IMAGE=2 FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 OPENPILOT_HACKS=1"
if arch == "Darwin":
return f'DEV=CPU HOME={os.path.expanduser("~")} IMAGE=0 FLOAT16=1'
if arch == "x86_64":
return "DEV=CPU:LLVM IMAGE=0 FLOAT16=1"
return "DEV=CPU:LLVM IMAGE=0 FLOAT16=1"
def _queue_metadata_generation(model_names, tinygrad_files):
if not PC:
return
inputs = tinygrad_files + [File(Dir("#iqpilot/selfdrive/iqmodeld/tools").File("install_models_pc.py").abspath)]
outputs = []
for model_name in model_names:
inputs.extend([File(f"models/{model_name}.onnx"), File(f"models/{model_name}_tinygrad.pkl")])
outputs.append(File(f"models/{model_name}_metadata.pkl"))
if outputs:
tool_dir = Dir("#iqpilot/selfdrive/iqmodeld/tools").abspath
model_dir = Dir("models").abspath
lenv.Command(outputs, inputs,
lenv.PrettyAction(f"${{PYWARN}} python3 {tool_dir}/install_models_pc.py {model_dir}", 'META'))
if arch == "Darwin":
frameworks += ["OpenCL"]
else:
libs += ["OpenCL"]
for symbol, filename in {"TRANSFORM": "transforms/warp_geometry.cl", "LOADYUV": "transforms/yuv.cl"}.items():
_inject_path_define(symbol, filename)
cython_libs = envCython["LIBS"] + libs
iqmodel_lib = lenv.Library("iqmodel", core_sources)
lenvCython.Program("native/iqmodel_pyx.so", "native/iqmodel_pyx.pyx", LIBS=[iqmodel_lib, *cython_libs], FRAMEWORKS=frameworks)
tinygrad_files = _tinygrad_sources()
present_models = _present_models()
_queue_metadata_generation(present_models, tinygrad_files)
def tg_compile(flags, model_name):
pythonpath_string = 'PYTHONPATH="${PYTHONPATH}:' + env.Dir("#tinygrad_repo").abspath + '"'
fn = File(f"models/{model_name}").abspath
return lenv.Command(
fn + "_tinygrad.pkl",
[fn + ".onnx"] + tinygrad_files,
lenv.PrettyAction(
f'${{PYWARN}} {pythonpath_string} {flags} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {fn}_tinygrad.pkl',
'MODEL', logfile='${TARGET}.log')
)
for model_name in present_models:
tg_compile(_tinygrad_flags(), model_name)
from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye
from openpilot.common.transformations.model import MEDMODEL_INPUT_SIZE
FUSED_CAMERA_CONFIGS = [(_ar_ox_fisheye.width, _ar_ox_fisheye.height), (_os_fisheye.width, _os_fisheye.height)]
FUSED_FRAME_SKIP = 4
def tg_compile_fused(file_prefix, flags):
pythonpath_string = 'PYTHONPATH="${PYTHONPATH}:' + env.Dir("#tinygrad_repo").abspath + '"'
model_dir = Dir("models").abspath
model_w, model_h = MEDMODEL_INPUT_SIZE
camera_args = " ".join(f"{cw}x{ch}" for cw, ch in FUSED_CAMERA_CONFIGS)
out_pkl = File(f"models/{file_prefix}driving_fused_tinygrad.pkl").abspath
onnx_deps = [File(f"models/{file_prefix}{model_name}.onnx") for model_name in FUSED_TRIPLET]
cmd = (
f'${{PYWARN}} {pythonpath_string} {flags} python3 {Dir("#iqpilot/selfdrive/iqmodeld/tools").abspath}/compile_daemon.py '
f'--model-size {model_w}x{model_h} --camera-resolutions {camera_args} '
f'--vision-onnx {model_dir}/{file_prefix}driving_vision.onnx '
f'--off-policy-onnx {model_dir}/{file_prefix}driving_off_policy.onnx '
f'--on-policy-onnx {model_dir}/{file_prefix}driving_on_policy.onnx '
f'--output {out_pkl} --frame-skip {FUSED_FRAME_SKIP}'
)
return lenv.Command(out_pkl, onnx_deps + tinygrad_files,
lenv.PrettyAction(cmd, 'MODEL', logfile='${TARGET}.log'))
if all(File(f"models/{model_name}.onnx").exists() for model_name in FUSED_TRIPLET):
tg_compile_fused("", _fused_flags())
if all(File(f"models/big_{model_name}.onnx").exists() for model_name in FUSED_TRIPLET):
tg_compile_fused("big_", "DEV=USB+AMD:LLVM WARP_DEV=QCOM FLOAT16=1 JIT_BATCH_SIZE=0 GMMU=0")

View File

@@ -0,0 +1,23 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
from pathlib import Path
def _models_dir() -> Path:
return Path(__file__).resolve().parent / "models"
def _artifact_path(stem: str, suffix: str) -> Path:
return _models_dir() / f"{stem}{suffix}"
MODEL_ASSETS = {
"onnx": _artifact_path("supercombo", ".onnx"),
"tinygrad": _artifact_path("supercombo", "_tinygrad.pkl"),
"metadata": _artifact_path("supercombo", "_metadata.pkl"),
}
MODEL_PATH = MODEL_ASSETS["onnx"]
MODEL_PKL_PATH = MODEL_ASSETS["tinygrad"]
METADATA_PATH = MODEL_ASSETS["metadata"]

View File

@@ -0,0 +1,68 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
import numpy as np
from openpilot.common.transformations.camera import DEVICE_CAMERAS
MAX_CAMERA_OFFSET_METERS = 0.35
class _OffsetSmoother:
def __init__(self, blend: float = 0.1):
self._blend = blend
self._value = 0.0
def step(self, target: float) -> float:
self._value = ((1.0 - self._blend) * self._value) + (self._blend * float(target))
return self._value
def _clamped_offset(raw_offset) -> float:
try:
parsed = float(raw_offset)
except (TypeError, ValueError):
parsed = 0.0
return float(np.clip(parsed, -MAX_CAMERA_OFFSET_METERS, MAX_CAMERA_OFFSET_METERS))
def _camera_profile(sm):
return DEVICE_CAMERAS[(str(sm["deviceState"].deviceType), str(sm["roadCameraState"].sensor))]
def _calibration_height(sm) -> float:
return sm["liveCalibration"].height[0] if sm["liveCalibration"].height else 1.22
def _sheared_transform(model_transform, intrinsics, height: float, lateral_offset: float):
optical_center_y = intrinsics[1, 2]
projection_bias = np.eye(3, dtype=np.float32)
projection_bias[0, 1] = lateral_offset / height
projection_bias[0, 2] = -(lateral_offset / height) * optical_center_y
return (projection_bias @ model_transform).astype(np.float32)
class CameraOffsetHelper:
def __init__(self):
self.camera_offset = 0.0
self.actual_camera_offset = 0.0
self._smoother = _OffsetSmoother()
@staticmethod
def apply_camera_offset(model_transform, intrinsics, height, offset_param):
return _sheared_transform(model_transform, intrinsics, height, offset_param)
def set_offset(self, offset):
self.camera_offset = _clamped_offset(offset)
def update(self, model_transform_main, model_transform_extra, sm, main_wide_camera, extra_uses_wide_camera=True):
self.actual_camera_offset = self._smoother.step(self.camera_offset)
camera_bundle = _camera_profile(sm)
camera_height = _calibration_height(sm)
main_intrinsics = camera_bundle.ecam.intrinsics if main_wide_camera else camera_bundle.fcam.intrinsics
extra_intrinsics = camera_bundle.ecam.intrinsics if extra_uses_wide_camera else camera_bundle.fcam.intrinsics
return (
self.apply_camera_offset(model_transform_main, main_intrinsics, camera_height, self.actual_camera_offset),
self.apply_camera_offset(model_transform_extra, extra_intrinsics, camera_height, self.actual_camera_offset),
)

View File

@@ -0,0 +1,130 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
from __future__ import annotations
import numpy as np
def _quadratic_series(limit: float, steps: int) -> list[float]:
peak_index = steps - 1
return [limit * ((index / peak_index) ** 2) for index in range(steps)]
def _probability_window(*values: float) -> np.ndarray:
return np.asarray(values, dtype=np.float32)
def _field_group(start: int, stop: int, stride: int) -> slice:
return slice(start, stop, stride)
_IDX_COUNT = 33
_T_AXIS = _quadratic_series(10.0, _IDX_COUNT)
_X_AXIS = _quadratic_series(192.0, _IDX_COUNT)
class ModelConstants:
IDX_N = _IDX_COUNT
T_IDXS = _T_AXIS
X_IDXS = _X_AXIS
LEAD_T_IDXS = [0.0, 2.0, 4.0, 6.0, 8.0, 10.0]
LEAD_T_OFFSETS = [0.0, 2.0, 4.0]
META_T_IDXS = [2.0, 4.0, 6.0, 8.0, 10.0]
MODEL_FREQ = 20
FEATURE_LEN = 512
FULL_HISTORY_BUFFER_LEN = 99
HISTORY_BUFFER_LEN = FULL_HISTORY_BUFFER_LEN
DESIRE_LEN = 8
TRAFFIC_CONVENTION_LEN = 2
NAV_FEATURE_LEN = 256
NAV_INSTRUCTION_LEN = 150
LAT_PLANNER_STATE_LEN = 4
LATERAL_CONTROL_PARAMS_LEN = 2
PREV_DESIRED_CURV_LEN = 1
FCW_THRESHOLDS_5MS2 = _probability_window(0.05, 0.05, 0.15, 0.15, 0.15)
FCW_THRESHOLDS_3MS2 = _probability_window(0.7, 0.7)
FCW_5MS2_PROBS_WIDTH = 5
FCW_3MS2_PROBS_WIDTH = 2
DISENGAGE_WIDTH = 5
POSE_WIDTH = 6
WIDE_FROM_DEVICE_WIDTH = 3
SIM_POSE_WIDTH = 6
LEAD_WIDTH = 4
LANE_LINES_WIDTH = 2
ROAD_EDGES_WIDTH = 2
PLAN_WIDTH = 15
DESIRE_PRED_WIDTH = 8
LAT_PLANNER_SOLUTION_WIDTH = 4
DESIRED_CURV_WIDTH = 1
NUM_LANE_LINES = 4
NUM_ROAD_EDGES = 2
LEAD_TRAJ_LEN = 6
DESIRE_PRED_LEN = 4
PLAN_MHP_N = 5
LEAD_MHP_N = 2
PLAN_MHP_SELECTION = 1
LEAD_MHP_SELECTION = 3
FCW_THRESHOLD_5MS2_HIGH = 0.15
FCW_THRESHOLD_5MS2_LOW = 0.05
FCW_THRESHOLD_3MS2 = 0.7
CONFIDENCE_BUFFER_LEN = 5
RYG_GREEN = 0.01165
RYG_YELLOW = 0.06157
POLY_PATH_DEGREE = 4
class Plan:
POSITION = slice(0, 3)
VELOCITY = slice(3, 6)
ACCELERATION = slice(6, 9)
T_FROM_CURRENT_EULER = slice(9, 12)
ORIENTATION_RATE = slice(12, 15)
class Meta:
ENGAGED = _field_group(0, 1, 1)
GAS_DISENGAGE = _field_group(1, 31, 6)
BRAKE_DISENGAGE = _field_group(2, 31, 6)
STEER_OVERRIDE = _field_group(3, 31, 6)
HARD_BRAKE_3 = _field_group(4, 31, 6)
HARD_BRAKE_4 = _field_group(5, 31, 6)
HARD_BRAKE_5 = _field_group(6, 31, 6)
GAS_PRESS = _field_group(31, 55, 4)
BRAKE_PRESS = _field_group(32, 55, 4)
LEFT_BLINKER = _field_group(33, 55, 4)
RIGHT_BLINKER = _field_group(34, 55, 4)
class MetaTombRaider:
ENGAGED = _field_group(0, 1, 1)
GAS_DISENGAGE = _field_group(1, 41, 8)
BRAKE_DISENGAGE = _field_group(2, 41, 8)
STEER_OVERRIDE = _field_group(3, 41, 8)
HARD_BRAKE_3 = _field_group(4, 41, 8)
HARD_BRAKE_4 = _field_group(5, 41, 8)
HARD_BRAKE_5 = _field_group(6, 41, 8)
GAS_PRESS = _field_group(7, 41, 8)
BRAKE_PRESS = _field_group(8, 41, 8)
LEFT_BLINKER = _field_group(41, 53, 2)
RIGHT_BLINKER = _field_group(42, 53, 2)
class MetaSimPose:
ENGAGED = _field_group(0, 1, 1)
GAS_DISENGAGE = _field_group(1, 36, 7)
BRAKE_DISENGAGE = _field_group(2, 36, 7)
STEER_OVERRIDE = _field_group(3, 36, 7)
HARD_BRAKE_3 = _field_group(4, 36, 7)
HARD_BRAKE_4 = _field_group(5, 36, 7)
HARD_BRAKE_5 = _field_group(6, 36, 7)
GAS_PRESS = _field_group(7, 36, 7)
LEFT_BLINKER = _field_group(36, 48, 2)
RIGHT_BLINKER = _field_group(37, 48, 2)

View File

@@ -0,0 +1,716 @@
#!/usr/bin/env python3
from __future__ import annotations
import time
from dataclasses import dataclass
from typing import Any
import cereal.messaging as messaging
import numpy as np
from cereal import car, custom, log
from cereal.messaging import PubMaster, SubMaster
from msgq.visionipc import VisionBuf, VisionIpcClient, VisionStreamType
from iqdbc.car.car_helpers import get_demo_car_params
from setproctitle import setproctitle
from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.common.iq_perf import PerfSample, PerfTraceEmitter, PerfTraceRing
from openpilot.common.params import Params
from openpilot.common.realtime import DT_MDL, config_realtime_process
from openpilot.common.swaglog import cloudlog
from openpilot.common.transformations.camera import DEVICE_CAMERAS
from openpilot.common.transformations.model import get_warp_matrix
from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper
from openpilot.selfdrive.controls.lib.drive_helpers import (
MODEL_SMOOTHING_MAX_TOTAL_SEC,
dynamic_lat_smooth_extra_seconds,
get_accel_from_plan,
smooth_value,
)
from openpilot.selfdrive.locationd.calibration_helpers import get_calibrated_rpy
from openpilot.system import sentry
from openpilot.iqpilot.common.steer_delay import resolve_steer_delay
from openpilot.iqpilot.selfdrive.iqmodeld.models.helpers import get_active_bundle
from openpilot.iqpilot.selfdrive.iqmodeld.models.inference_state import InferenceStateBase
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.model_runner import get_model_runner
from openpilot.iqpilot.selfdrive.iqmodeld.camera import CameraOffsetHelper
from openpilot.iqpilot.selfdrive.iqmodeld.config import Plan
from openpilot.iqpilot.selfdrive.iqmodeld.messaging import (
DrivePacketMemory,
pick_curvature,
populate_drive_messages,
populate_odometry_message,
)
from openpilot.iqpilot.selfdrive.iqmodeld.metadata import select_meta_layout
try:
from openpilot.iqpilot.selfdrive.iqmodeld.native.iqmodel_pyx import RoadProjector, WarpContext
except ModuleNotFoundError:
class WarpContext:
def __init__(self, *args, **kwargs):
raise ModuleNotFoundError("openpilot.iqpilot.selfdrive.iqmodeld.native.iqmodel_pyx is not built")
class RoadProjector:
def __init__(self, *args, **kwargs):
raise ModuleNotFoundError("openpilot.iqpilot.selfdrive.iqmodeld.native.iqmodel_pyx is not built")
PROCESS_NAME = "iqpilot.selfdrive.iqmodeld.daemon"
IQP_NAV_MODEL_INFLUENCE_ENABLED = False
TurnDirection = custom.IQTurnSignalDirection
IQMODEL_EVAL_WARN_US = int(DT_MDL * 1_000_000)
IQMODEL_EVAL_ERROR_US = IQMODEL_EVAL_WARN_US * 2
def _plan_y_std_1s(outputs: dict[str, np.ndarray]) -> float:
# plan_stds is (batch, IDX_N, PLAN_WIDTH); index 10 ~= 1s ahead (see ModelConstants.T_IDXS),
# POSITION is an (x, y, z) slice within PLAN_WIDTH so [1] picks the lateral (y) std.
try:
return float(outputs["plan_stds"][0, 10, Plan.POSITION][1])
except (KeyError, IndexError):
return 0.0
def _model_lat_smooth_max_sec(params: Params) -> float:
if not params.get_bool("ModelSmoothingEnabled"):
return 0.0
try:
raw = params.get("ModelLatSmoothSec", return_default=True)
raw = 0 if raw is None else int(raw)
except (ValueError, TypeError):
raw = 0
return min(max(raw, 0), 30) * 0.01
@dataclass
class CaptureStamp:
frame_id: int = 0
timestamp_sof: int = 0
timestamp_eof: int = 0
@classmethod
def from_vipc(cls, client: VisionIpcClient) -> "CaptureStamp":
return cls(client.frame_id, client.timestamp_sof, client.timestamp_eof)
@dataclass(frozen=True)
class StreamLayout:
dual_camera: bool
main_is_wide: bool
primary_stream: VisionStreamType
class ReplayLedger:
def __init__(self, tensor_shapes: dict[str, tuple[int, ...]], frame_inputs: list[str]):
self.inputs: dict[str, np.ndarray] = {}
self.archive: dict[str, np.ndarray] = {}
self.selectors: dict[str, np.ndarray] = {}
self._frame_inputs = set(frame_inputs)
self._pulse_name: str | None = None
self._pulse_memory: np.ndarray | None = None
feature_shape = tensor_shapes.get("features_buffer")
for tensor_name, tensor_shape in tensor_shapes.items():
if tensor_name in self._frame_inputs:
continue
self.inputs[tensor_name] = np.zeros(tensor_shape, dtype=np.float32)
if len(tensor_shape) != 3 or tensor_shape[1] <= 1:
continue
history_len = self._history_length(tensor_shape, feature_shape)
self.archive[tensor_name] = np.zeros((1, history_len, tensor_shape[2]), dtype=np.float32)
export_index = self._export_index(tensor_shape, history_len, feature_shape)
if export_index is not None:
self.selectors[tensor_name] = export_index
if tensor_name.startswith("desire"):
self._pulse_name = tensor_name
self._pulse_memory = np.zeros(tensor_shape[2], dtype=np.float32)
@staticmethod
def _history_length(tensor_shape: tuple[int, ...], feature_shape: tuple[int, ...] | None) -> int:
if tensor_shape[1] >= 99:
return tensor_shape[1]
if tensor_shape[1] in (24, 25) and feature_shape is not None and feature_shape[1] == 24:
return (feature_shape[1] + 1) * 4
return tensor_shape[1] * 4
@staticmethod
def _export_index(tensor_shape: tuple[int, ...], history_len: int,
feature_shape: tuple[int, ...] | None) -> np.ndarray | None:
if tensor_shape[1] in (24, 25) and feature_shape is not None and feature_shape[1] == 24:
stride = int(-history_len / tensor_shape[1])
return np.arange(stride, stride * (tensor_shape[1] + 1), stride)[::-1]
if tensor_shape[1] == 25:
skip = history_len // tensor_shape[1]
return np.arange(history_len)[-1 - (skip * (tensor_shape[1] - 1))::skip]
if tensor_shape[1] >= 99:
return np.arange(tensor_shape[1])
return None
@property
def pulse_name(self) -> str:
if self._pulse_name is None:
raise KeyError("No desire-like pulse input present in model inputs")
return self._pulse_name
def _shift_archive(self, tensor_name: str) -> np.ndarray:
history = self.archive[tensor_name]
history[0, :-1] = history[0, 1:]
return history
def inject_pulse(self, pulse_values: np.ndarray) -> None:
pulse = pulse_values.copy()
pulse[0] = 0
assert self._pulse_memory is not None
rising = np.where(pulse - self._pulse_memory > 0.99, pulse, 0)
self._pulse_memory[:] = pulse
history = self._shift_archive(self.pulse_name)
history[0, -1] = rising
exported_shape = self.inputs[self.pulse_name].shape
if history.shape[1] > exported_shape[1]:
stride = history.shape[1] // exported_shape[1]
self.inputs[self.pulse_name][:] = history[0].reshape(
exported_shape[0], exported_shape[1], stride, -1
).max(axis=2)
return
self.inputs[self.pulse_name][:] = history[0, self.selectors[self.pulse_name]]
def merge_inputs(self, fresh_inputs: dict[str, np.ndarray]) -> None:
pulse_name = self.pulse_name
for tensor_name, tensor_value in fresh_inputs.items():
if tensor_name in self.inputs and tensor_name != pulse_name:
self.inputs[tensor_name][:] = tensor_value
def note_hidden_state(self, hidden_state: np.ndarray) -> None:
if "features_buffer" not in self.archive:
return
history = self._shift_archive("features_buffer")
history[0, -1] = hidden_state[0]
self.inputs["features_buffer"][:] = history[0, self.selectors["features_buffer"]]
def note_feedback(self, tensor_name: str, values: np.ndarray, zero_export: bool = False) -> None:
if tensor_name not in self.archive:
return
history = self._shift_archive(tensor_name)
history[0, -1, :] = values[0]
exported = history[0, self.selectors[tensor_name]]
self.inputs[tensor_name][:] = 0 * exported if zero_export else exported
def _planplus_gain(vehicle_speed: float) -> float:
return 0.75 if vehicle_speed >= 25.0 else 1.0
def _merged_plan(runtime_state: "NeuralEngineState", outputs: dict[str, np.ndarray], vehicle_speed: float) -> np.ndarray:
base_plan = outputs["plan"][0]
if "planplus" not in outputs:
return base_plan
return base_plan + (runtime_state.PLANPLUS_CONTROL * _planplus_gain(vehicle_speed)) * outputs["planplus"][0]
class NeuralEngineState(InferenceStateBase):
frames: dict[str, RoadProjector]
def __init__(self, gpu_context: WarpContext):
super().__init__()
runner = get_model_runner()
bundle = get_active_bundle()
self.model_runner = runner
self.constants = runner.constants
self.generation = bundle.generation if bundle is not None else None
knob_values = {entry.key: entry.value for entry in bundle.overrides} if bundle is not None else {}
self.LAT_SMOOTH_SECONDS = float(knob_values.get("lat", ".0"))
self.LONG_SMOOTH_SECONDS = float(knob_values.get("long", ".0"))
self.MIN_LAT_CONTROL_SPEED = 0.3
self.PLANPLUS_CONTROL = 1.0
self.model_smoothing_max_extra_sec = 0.0
context_depth = 5 if runner.is_20hz else 2
self.frames = {
stream_name: RoadProjector(gpu_context, context_depth)
for stream_name in runner.vision_input_names
}
self._ledger = ReplayLedger(runner.input_shapes, runner.vision_input_names)
self.numpy_inputs = self._ledger.inputs
self.temporal_buffers = self._ledger.archive
self.temporal_idxs_map = self._ledger.selectors
@property
def mlsim(self) -> bool:
return bool(self.generation is not None and self.generation >= 11)
@property
def desire_key(self) -> str:
return self._ledger.pulse_name
def _warp_frames(self, vision_bufs: dict[str, VisionBuf],
transform_map: dict[str, np.ndarray]) -> dict[str, Any]:
return {
stream_name: self.frames[stream_name].stage(vision_bufs[stream_name], transform_map[stream_name].flatten())
for stream_name in self.model_runner.vision_input_names
}
def _run_split_model(self) -> dict[str, np.ndarray]:
if hasattr(self.model_runner, "run_vision"):
vision_packet = self.model_runner.run_vision()
self._ledger.note_hidden_state(vision_packet["hidden_state"])
self.model_runner.refresh_policy_features(self.numpy_inputs["features_buffer"])
return {**vision_packet, **self.model_runner.run_policy()}
result = self.model_runner.run_model()
if "hidden_state" in result:
self._ledger.note_hidden_state(result["hidden_state"])
return result
def _write_curvature_memory(self, outputs: dict[str, np.ndarray]) -> None:
if "desired_curvature" not in outputs:
return
feedback_slot = None
if "prev_desired_curvs" in self.numpy_inputs:
feedback_slot = "prev_desired_curvs"
elif "prev_desired_curv" in self.numpy_inputs:
feedback_slot = "prev_desired_curv"
if feedback_slot is not None:
self._ledger.note_feedback(feedback_slot, outputs["desired_curvature"], zero_export=self.mlsim)
def run(self, vision_bufs: dict[str, VisionBuf], transform_map: dict[str, np.ndarray],
fresh_inputs: dict[str, np.ndarray]) -> dict[str, np.ndarray] | None:
if not getattr(self.model_runner, "uses_opencl_warp", True):
return self.model_runner.run_fused(vision_bufs, transform_map, fresh_inputs)
self._ledger.inject_pulse(fresh_inputs[self.desire_key])
self._ledger.merge_inputs(fresh_inputs)
warped_frames = self._warp_frames(vision_bufs, transform_map)
self.model_runner.prepare_inputs(warped_frames, self.numpy_inputs, self.frames)
outputs = self._run_split_model()
self._write_curvature_memory(outputs)
return outputs
def get_action_from_model(self, outputs: dict[str, np.ndarray], previous_action: log.ModelDataV2.Action,
lat_action_t: float, long_action_t: float, vehicle_speed: float,
lat_smooth_seconds: float | None = None) -> log.ModelDataV2.Action:
if lat_smooth_seconds is None:
lat_smooth_seconds = self.LAT_SMOOTH_SECONDS
if "action" in outputs:
curvature_cmd = outputs["action"][0, 0] / (max(1.0, vehicle_speed)) ** 2
accel_cmd = outputs["action"][0, 1]
should_stop = bool(vehicle_speed < 0.3 and accel_cmd < 0.1)
accel_cmd = smooth_value(accel_cmd, previous_action.desiredAcceleration, self.LONG_SMOOTH_SECONDS)
if vehicle_speed > self.MIN_LAT_CONTROL_SPEED:
curvature_cmd = smooth_value(curvature_cmd, previous_action.desiredCurvature, lat_smooth_seconds)
else:
curvature_cmd = previous_action.desiredCurvature
return log.ModelDataV2.Action(
desiredCurvature=float(curvature_cmd),
desiredAcceleration=float(accel_cmd),
shouldStop=should_stop,
)
plan_rows = _merged_plan(self, outputs, vehicle_speed)
accel_cmd, should_stop = get_accel_from_plan(
plan_rows[:, Plan.VELOCITY][:, 0],
plan_rows[:, Plan.ACCELERATION][:, 0],
self.constants.T_IDXS,
action_t=long_action_t,
)
accel_cmd = smooth_value(accel_cmd, previous_action.desiredAcceleration, self.LONG_SMOOTH_SECONDS)
curvature_cmd = pick_curvature(outputs, plan_rows, vehicle_speed, lat_action_t, self.mlsim)
if self.generation is not None and self.generation >= 10:
if vehicle_speed > self.MIN_LAT_CONTROL_SPEED:
curvature_cmd = smooth_value(curvature_cmd, previous_action.desiredCurvature, lat_smooth_seconds)
else:
curvature_cmd = previous_action.desiredCurvature
return log.ModelDataV2.Action(
desiredCurvature=float(curvature_cmd),
desiredAcceleration=float(accel_cmd),
shouldStop=bool(should_stop),
)
class CameraIngress:
def __init__(self, gpu_context: WarpContext):
self.layout = self._discover_layout()
self._primary = VisionIpcClient("camerad", self.layout.primary_stream, True, gpu_context)
self._secondary = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_WIDE_ROAD, False, gpu_context)
while not self._primary.connect(False):
time.sleep(0.1)
while self.layout.dual_camera and not self._secondary.connect(False):
time.sleep(0.1)
cloudlog.warning(
f"connected main cam with buffer size: {self._primary.buffer_len} ({self._primary.width} x {self._primary.height})"
)
if self.layout.dual_camera:
cloudlog.warning(
f"connected extra cam with buffer size: {self._secondary.buffer_len} ({self._secondary.width} x {self._secondary.height})"
)
@staticmethod
def _discover_layout() -> StreamLayout:
while True:
available = VisionIpcClient.available_streams("camerad", block=False)
if available:
dual_camera = (
VisionStreamType.VISION_STREAM_WIDE_ROAD in available
and VisionStreamType.VISION_STREAM_ROAD in available
)
main_is_wide = VisionStreamType.VISION_STREAM_ROAD not in available
primary_stream = VisionStreamType.VISION_STREAM_WIDE_ROAD if main_is_wide else VisionStreamType.VISION_STREAM_ROAD
cloudlog.warning(
f"vision stream set up, main_wide_camera: {main_is_wide}, use_extra_client: {dual_camera}"
)
return StreamLayout(dual_camera=dual_camera, main_is_wide=main_is_wide, primary_stream=primary_stream)
time.sleep(0.1)
def pull(self) -> tuple[VisionBuf, VisionBuf, CaptureStamp, CaptureStamp] | None:
main_buf = None
wide_buf = None
main_stamp = CaptureStamp()
wide_stamp = CaptureStamp()
while main_stamp.timestamp_sof < wide_stamp.timestamp_sof + 25000000:
main_buf = self._primary.recv()
main_stamp = CaptureStamp.from_vipc(self._primary)
if main_buf is None:
return None
if not self.layout.dual_camera:
return main_buf, main_buf, main_stamp, main_stamp
while True:
wide_buf = self._secondary.recv()
wide_stamp = CaptureStamp.from_vipc(self._secondary)
if wide_buf is None or main_stamp.timestamp_sof < wide_stamp.timestamp_sof + 25000000:
break
if wide_buf is None:
return None
if abs(main_stamp.timestamp_sof - wide_stamp.timestamp_sof) > 10000000:
cloudlog.error(
f"frames out of sync! main: {main_stamp.frame_id} ({main_stamp.timestamp_sof / 1e9:.5f}),"
f" extra: {wide_stamp.frame_id} ({wide_stamp.timestamp_sof / 1e9:.5f})"
)
return main_buf, wide_buf, main_stamp, wide_stamp
class CalibrationAtlas:
def __init__(self):
self.main_warp = np.zeros((3, 3), dtype=np.float32)
self.extra_warp = np.zeros((3, 3), dtype=np.float32)
self.ready = False
self._offset_tuner = CameraOffsetHelper()
def set_offset(self, offset_value: Any) -> None:
self._offset_tuner.set_offset(offset_value)
def refresh(self, sm: SubMaster, main_is_wide: bool, dual_camera: bool) -> tuple[np.ndarray, np.ndarray, bool]:
if not (sm.seen["liveCalibration"] and sm.seen["roadCameraState"] and sm.seen["deviceState"]):
return self.main_warp, self.extra_warp, self.ready
rpy = get_calibrated_rpy(sm["liveCalibration"])
if rpy is None:
live_calib = sm["liveCalibration"]
if len(live_calib.rpyCalib) == 3:
rpy = np.array(live_calib.rpyCalib, dtype=np.float32)
else:
rpy = np.zeros(3, dtype=np.float32)
device_key = (str(sm["deviceState"].deviceType), str(sm["roadCameraState"].sensor))
device_camera = DEVICE_CAMERAS[device_key]
main_intrinsics = device_camera.ecam.intrinsics if main_is_wide else device_camera.fcam.intrinsics
extra_uses_wide_camera = dual_camera or main_is_wide
extra_intrinsics = device_camera.ecam.intrinsics if extra_uses_wide_camera else device_camera.fcam.intrinsics
self.main_warp = get_warp_matrix(rpy, main_intrinsics, False).astype(np.float32)
self.extra_warp = get_warp_matrix(rpy, extra_intrinsics, True).astype(np.float32)
self.main_warp, self.extra_warp = self._offset_tuner.update(
self.main_warp, self.extra_warp, sm, main_is_wide, extra_uses_wide_camera
)
self.ready = True
return self.main_warp, self.extra_warp, self.ready
class FrameDropMeter:
def __init__(self, model_freq: float):
self._smoother = FirstOrderFilter(0.0, 10.0, 1.0 / model_freq)
self._warm_frames = 0
self._last_frame_id = 0
def sample(self, frame_id: int) -> tuple[int, float, bool]:
dropped = max(0, frame_id - self._last_frame_id - 1)
smooth = self._smoother.update(min(dropped, 10))
if self._warm_frames < 10:
self._smoother.x = 0.0
smooth = 0.0
self._warm_frames += 1
return dropped, smooth / (1 + smooth), dropped > 0
def commit(self, frame_id: int) -> None:
self._last_frame_id = frame_id
class InferenceDaemon:
def __init__(self, demo: bool = False):
cloudlog.warning("iqmodeld init")
sentry.set_tag("daemon", PROCESS_NAME)
cloudlog.bind(daemon=PROCESS_NAME)
setproctitle(PROCESS_NAME)
config_realtime_process(7, 54)
cloudlog.warning("setting up CL context")
self._gpu = WarpContext()
cloudlog.warning("CL context ready; loading model")
self._runtime = NeuralEngineState(self._gpu)
self._meta_layout = select_meta_layout()
cloudlog.warning("models loaded, iqmodeld starting")
self._cameras = CameraIngress(self._gpu)
self._pub = PubMaster(["modelV2", "drivingModelData", "cameraOdometry", "iqDriveModelData", "iqPerfTrace"])
self._sub = SubMaster([
"deviceState", "carState", "roadCameraState", "liveCalibration",
"driverMonitoringState", "carControl", "liveDelay", "iqNavState", "radarState",
])
self._message_memory = DrivePacketMemory()
self._params = Params()
self._frame_meter = FrameDropMeter(self._runtime.constants.MODEL_FREQ)
self._warps = CalibrationAtlas()
self._perf = PerfTraceEmitter("iqmodeld", pubmaster=self._pub)
self._perf_ring = PerfTraceRing()
self._car_params = self._load_car_params(demo)
self._long_action_delay = self._car_params.longitudinalActuatorDelay + self._runtime.LONG_SMOOTH_SECONDS
self._previous_action = log.ModelDataV2.Action()
self._desire_logic = DesireHelper()
self._lat_smooth_extra_sec = 0.0
def _load_car_params(self, demo: bool):
car_params = get_demo_car_params() if demo else messaging.log_from_bytes(
self._params.get("CarParams", block=True), car.CarParams)
cloudlog.info("iqmodeld got CarParams: %s", car_params.brand)
return car_params
def _refresh_tunables(self, tick: int) -> None:
if tick % 60 != 0:
return
self._runtime.lat_delay = resolve_steer_delay(self._params, self._sub["liveDelay"].lateralDelay)
self._runtime.PLANPLUS_CONTROL = self._params.get("PlanplusControl", return_default=True)
self._runtime.model_smoothing_max_extra_sec = _model_lat_smooth_max_sec(self._params)
self._warps.set_offset(self._params.get("CameraOffset", return_default=True))
def _traffic_side(self) -> np.ndarray:
traffic = np.zeros(2, dtype=np.float32)
traffic[int(self._sub["driverMonitoringState"].isRHD)] = 1
return traffic
def _desire_pulse(self) -> np.ndarray:
pulse = np.zeros(self._runtime.constants.DESIRE_LEN, dtype=np.float32)
desire_idx = self._desire_logic.desire
if 0 <= desire_idx < self._runtime.constants.DESIRE_LEN:
pulse[desire_idx] = 1
return pulse
def _compose_inputs(self, vehicle_speed: float, lat_horizon: float, long_horizon: float) -> dict[str, np.ndarray]:
inputs: dict[str, np.ndarray] = {
self._runtime.desire_key: self._desire_pulse(),
"traffic_convention": self._traffic_side(),
}
if "lateral_control_params" in self._runtime.numpy_inputs:
inputs["lateral_control_params"] = np.array([vehicle_speed, lat_horizon], dtype=np.float32)
if "action_t" in self._runtime.numpy_inputs:
inputs["action_t"] = np.array([lat_horizon, long_horizon], dtype=np.float32)
return inputs
def _publish(self, outputs: dict[str, np.ndarray], main_stamp: CaptureStamp, extra_stamp: CaptureStamp,
road_frame_id: int, frame_drop_ratio: float, dropped_frames: int,
execution_time: float, live_calib_seen: bool,
lat_horizon: float, long_horizon: float, vehicle_speed: float) -> None:
model_msg = messaging.new_message("modelV2")
driving_msg = messaging.new_message("drivingModelData")
pose_msg = messaging.new_message("cameraOdometry")
iq_msg = messaging.new_message("iqDriveModelData")
self._lat_smooth_extra_sec = dynamic_lat_smooth_extra_seconds(
_plan_y_std_1s(outputs), self._runtime.model_smoothing_max_extra_sec
)
lat_smooth_total_sec = min(self._runtime.LAT_SMOOTH_SECONDS + self._lat_smooth_extra_sec, MODEL_SMOOTHING_MAX_TOTAL_SEC)
action = self._runtime.get_action_from_model(
outputs, self._previous_action, lat_horizon, long_horizon, vehicle_speed, lat_smooth_total_sec
)
self._previous_action = action
populate_drive_messages(
driving_msg,
model_msg,
outputs,
action,
self._message_memory,
main_stamp.frame_id,
extra_stamp.frame_id,
road_frame_id,
frame_drop_ratio,
main_stamp.timestamp_eof,
execution_time,
live_calib_seen,
self._meta_layout,
)
desire_state = model_msg.modelV2.meta.desireState
lane_change_prob = desire_state[log.Desire.laneChangeLeft] + desire_state[log.Desire.laneChangeRight]
self._desire_logic.update(
self._sub["carState"],
self._sub["carControl"].latActive,
lane_change_prob,
self._sub["iqNavState"],
model_msg.modelV2,
self._sub["radarState"],
)
model_msg.modelV2.meta.laneChangeState = self._desire_logic.lane_change_state
model_msg.modelV2.meta.laneChangeDirection = self._desire_logic.lane_change_direction
driving_msg.drivingModelData.meta.laneChangeState = self._desire_logic.lane_change_state
driving_msg.drivingModelData.meta.laneChangeDirection = self._desire_logic.lane_change_direction
iq_msg.iqDriveModelData.turnSignalDirection = self._desire_logic.lane_turn_direction
populate_odometry_message(
pose_msg,
outputs,
main_stamp.frame_id,
dropped_frames,
main_stamp.timestamp_eof,
live_calib_seen,
)
self._pub.send("modelV2", model_msg)
self._pub.send("drivingModelData", driving_msg)
self._pub.send("cameraOdometry", pose_msg)
self._pub.send("iqDriveModelData", iq_msg)
def serve(self) -> None:
tick = 0
while True:
frame_pair = self._cameras.pull()
if frame_pair is None:
cloudlog.debug("visionipc frame missing")
continue
main_buf, extra_buf, main_stamp, extra_stamp = frame_pair
self._sub.update(0)
self._refresh_tunables(tick)
vehicle_speed = max(self._sub["carState"].vEgo, 0.0)
lat_horizon = self._runtime.lat_delay + self._runtime.LAT_SMOOTH_SECONDS + self._lat_smooth_extra_sec + DT_MDL
long_horizon = self._long_action_delay + DT_MDL
main_warp, extra_warp, live_calib_seen = self._warps.refresh(
self._sub, self._cameras.layout.main_is_wide, self._cameras.layout.dual_camera
)
dropped_frames, frame_drop_ratio, prepare_only = self._frame_meter.sample(main_stamp.frame_id)
vision_bufs = {
stream_name: extra_buf if "big" in stream_name else main_buf
for stream_name in self._runtime.model_runner.vision_input_names
}
warp_map = {
stream_name: extra_warp if "big" in stream_name else main_warp
for stream_name in self._runtime.model_runner.vision_input_names
}
fresh_inputs = self._compose_inputs(vehicle_speed, lat_horizon, long_horizon)
started_at = time.perf_counter()
outputs = self._runtime.run(vision_bufs, warp_map, fresh_inputs)
execution_time = time.perf_counter() - started_at
execution_us = int(execution_time * 1_000_000)
sample = PerfSample(
frame_id=main_stamp.frame_id,
model_eval_us=execution_us,
model_dropped_frames=dropped_frames,
model_backlog=max(0, dropped_frames),
)
self._perf_ring.push(sample)
if dropped_frames > 0 or execution_us >= IQMODEL_EVAL_WARN_US:
severity = "warning"
if dropped_frames > 0 or execution_us >= IQMODEL_EVAL_ERROR_US:
severity = "error"
self._perf.emit(
"iqmodeld_dropped_frames" if dropped_frames > 0 else "iqmodeld_slow_eval",
severity=severity,
frame_id=main_stamp.frame_id,
total_time_us=execution_us,
dropped_frames=dropped_frames,
backlog=max(0, dropped_frames),
samples=self._perf_ring.snapshot(),
detail=(
f"model_eval_us={execution_us} dropped_frames={dropped_frames} prepare_only={int(prepare_only)} "
f"road_frame_id={self._sub['roadCameraState'].frameId}"
),
min_interval_s=0.25,
)
if outputs is not None:
self._publish(
outputs,
main_stamp,
extra_stamp,
self._sub["roadCameraState"].frameId,
frame_drop_ratio,
dropped_frames,
execution_time,
live_calib_seen,
lat_horizon,
long_horizon,
vehicle_speed,
)
self._frame_meter.commit(main_stamp.frame_id)
tick += 1
def main(demo: bool = False):
InferenceDaemon(demo=demo).serve()
__all__ = [
"PROCESS_NAME",
"IQP_NAV_MODEL_INFLUENCE_ENABLED",
"TurnDirection",
"CaptureStamp",
"ReplayLedger",
"NeuralEngineState",
"CameraIngress",
"CalibrationAtlas",
"FrameDropMeter",
"InferenceDaemon",
"main",
]
if __name__ == "__main__":
try:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--demo", action="store_true", help="Run iqmodeld in demo mode.")
args = parser.parse_args()
main(demo=args.demo)
except KeyboardInterrupt:
cloudlog.warning(f"child {PROCESS_NAME} got SIGINT")
except Exception:
sentry.capture_exception()
raise

View File

@@ -0,0 +1,62 @@
{
"displayName": "Default (CD210)",
"environment": "development",
"generation": 12,
"index": 56,
"internalName": "C210M",
"is20hz": true,
"minimumSelectorVersion": 14,
"models": [
{
"artifact": {
"downloadUri": {
"sha256": "ba5c459412310a8c65a11e02cb1f522fe439d515589754451561268f028f4fb0",
"uri": "https://git.konn3kt.com/teal/IQModels/raw/branch/main/models/recompiled16/model-CD210%20Model%20%28January%2031%2C%202026%29-101/driving_policy_c210m_tinygrad.pkl"
},
"fileName": "driving_policy_c210m_tinygrad.pkl"
},
"metadata": {
"downloadUri": {
"sha256": "15c8c1ad9073424ee1101b1f7170421140ed308ebaa7c917001130fb1760420b",
"uri": "https://git.konn3kt.com/teal/IQModels/raw/branch/main/models/recompiled16/model-CD210%20Model%20%28January%2031%2C%202026%29-101/driving_policy_c210m_metadata.pkl"
},
"fileName": "driving_policy_c210m_metadata.pkl"
},
"type": "policy"
},
{
"artifact": {
"downloadUri": {
"sha256": "c6c9cfba6c618a361d474d2fe11c4d8e7a249e9311dd9bcb42b287067ef75ae7",
"uri": "https://git.konn3kt.com/teal/IQModels/raw/branch/main/models/recompiled16/model-CD210%20Model%20%28January%2031%2C%202026%29-101/driving_vision_c210m_tinygrad.pkl"
},
"fileName": "driving_vision_c210m_tinygrad.pkl"
},
"metadata": {
"downloadUri": {
"sha256": "a2be39088d38550e818f5ac1c6300a64605ba0bd0676a27fe7bbea9fddae90a1",
"uri": "https://git.konn3kt.com/teal/IQModels/raw/branch/main/models/recompiled16/model-CD210%20Model%20%28January%2031%2C%202026%29-101/driving_vision_c210m_metadata.pkl"
},
"fileName": "driving_vision_c210m_metadata.pkl"
},
"type": "vision"
}
],
"overrides": [
{
"key": "folder",
"value": "Master Models"
},
{
"key": "lat",
"value": ".0"
},
{
"key": "long",
"value": ".3"
}
],
"ref": "default",
"runner": "tinygrad",
"status": "notDownloading"
}

View File

@@ -0,0 +1,16 @@
#!/usr/bin/env bash
# Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
set -euo pipefail
script_home() {
cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd
}
main() {
local here
here="$(script_home)"
exec "$here/daemon.py" "$@"
}
main "$@"

View File

@@ -0,0 +1,269 @@
from __future__ import annotations
import os
from dataclasses import dataclass, field
import capnp
import numpy as np
from cereal import log
from openpilot.iqpilot.selfdrive.iqmodeld.models.helpers import plan_x_idxs_helper
from openpilot.iqpilot.selfdrive.iqmodeld.config import ModelConstants, Plan
from openpilot.selfdrive.controls.lib.drive_helpers import get_curvature_from_plan
SEND_RAW_PRED = os.getenv("SEND_RAW_PRED")
ConfidenceClass = log.ModelDataV2.ConfidenceClass
def pick_curvature(outputs: dict[str, np.ndarray], plan_rows: np.ndarray, vehicle_speed: float,
action_horizon: float, synthetic_lane_logic: bool) -> float:
direct_signal = None if synthetic_lane_logic else outputs.get("desired_curvature")
if direct_signal is not None:
return float(direct_signal[0, 0])
yaw_track = plan_rows[:, Plan.T_FROM_CURRENT_EULER][:, 2]
yaw_rate_track = plan_rows[:, Plan.ORIENTATION_RATE][:, 2]
return float(get_curvature_from_plan(yaw_track, yaw_rate_track, ModelConstants.T_IDXS, vehicle_speed, action_horizon))
@dataclass
class DrivePacketMemory:
disengage_rollup: np.ndarray = field(default_factory=lambda: np.zeros(
ModelConstants.CONFIDENCE_BUFFER_LEN * ModelConstants.DISENGAGE_WIDTH, dtype=np.float32))
brake_watch_5: np.ndarray = field(default_factory=lambda: np.zeros(
ModelConstants.FCW_5MS2_PROBS_WIDTH, dtype=np.float32))
brake_watch_3: np.ndarray = field(default_factory=lambda: np.zeros(
ModelConstants.FCW_3MS2_PROBS_WIDTH, dtype=np.float32))
def _assign_xyz(builder, t_points, x_track, y_track, z_track,
x_std=None, y_std=None, z_std=None) -> None:
builder.t = t_points
builder.x = x_track.tolist()
builder.y = y_track.tolist()
builder.z = z_track.tolist()
if x_std is not None:
builder.xStd = x_std.tolist()
if y_std is not None:
builder.yStd = y_std.tolist()
if z_std is not None:
builder.zStd = z_std.tolist()
def _assign_xyva(builder, t_points, x_track, y_track, v_track, a_track,
x_std=None, y_std=None, v_std=None, a_std=None) -> None:
builder.t = t_points
builder.x = x_track.tolist()
builder.y = y_track.tolist()
builder.v = v_track.tolist()
builder.a = a_track.tolist()
if x_std is not None:
builder.xStd = x_std.tolist()
if y_std is not None:
builder.yStd = y_std.tolist()
if v_std is not None:
builder.vStd = v_std.tolist()
if a_std is not None:
builder.aStd = a_std.tolist()
def _fit_path(builder, degree: int, x_track: np.ndarray, y_track: np.ndarray, z_track: np.ndarray) -> None:
stacked = np.stack([x_track, y_track, z_track], axis=1)
coeffs = np.polynomial.polynomial.polyfit(ModelConstants.T_IDXS, stacked, deg=degree)
builder.xCoefficients = coeffs[:, 0].tolist()
builder.yCoefficients = coeffs[:, 1].tolist()
builder.zCoefficients = coeffs[:, 2].tolist()
def _lane_snapshot(builder, lane_lines, lane_probs: list[float]) -> None:
builder.leftY = lane_lines[1].y[0]
builder.leftProb = lane_probs[1]
builder.rightY = lane_lines[2].y[0]
builder.rightProb = lane_probs[2]
def _roll_brake_watch(outputs: dict[str, np.ndarray], memory: DrivePacketMemory, meta_layout) -> bool:
memory.brake_watch_5[:-1] = memory.brake_watch_5[1:]
memory.brake_watch_5[-1] = outputs["meta"][0, meta_layout.HARD_BRAKE_5][0]
memory.brake_watch_3[:-1] = memory.brake_watch_3[1:]
memory.brake_watch_3[-1] = outputs["meta"][0, meta_layout.HARD_BRAKE_3][0]
return bool(
(memory.brake_watch_5 > ModelConstants.FCW_THRESHOLDS_5MS2).all()
and (memory.brake_watch_3 > ModelConstants.FCW_THRESHOLDS_3MS2).all()
)
def _confidence_bucket(outputs: dict[str, np.ndarray], memory: DrivePacketMemory, meta_layout, frame_id: int):
width = ModelConstants.DISENGAGE_WIDTH
if frame_id % (2 * ModelConstants.MODEL_FREQ) == 0:
brake_probs = outputs["meta"][0, meta_layout.BRAKE_DISENGAGE]
gas_probs = outputs["meta"][0, meta_layout.GAS_DISENGAGE]
steer_probs = outputs["meta"][0, meta_layout.STEER_OVERRIDE]
takeover_curve = 1 - ((1 - brake_probs) * (1 - gas_probs) * (1 - steer_probs))
independent = np.r_[takeover_curve[0], np.diff(takeover_curve) / (1 - takeover_curve[:-1])]
memory.disengage_rollup[:-width] = memory.disengage_rollup[width:]
memory.disengage_rollup[-width:] = independent
score = 0.0
for idx in range(width):
score += memory.disengage_rollup[idx * width + width - 1 - idx].item() / width
if score < ModelConstants.RYG_GREEN:
return ConfidenceClass.green
if score < ModelConstants.RYG_YELLOW:
return ConfidenceClass.yellow
return ConfidenceClass.red
def _write_plan_family(model_packet, driving_packet, outputs: dict[str, np.ndarray]) -> None:
plan_rows = outputs["plan"][0]
plan_stds = outputs["plan_stds"][0]
_assign_xyz(model_packet.position, ModelConstants.T_IDXS, *plan_rows[:, Plan.POSITION].T, *plan_stds[:, Plan.POSITION].T)
_assign_xyz(model_packet.velocity, ModelConstants.T_IDXS, *plan_rows[:, Plan.VELOCITY].T)
_assign_xyz(model_packet.acceleration, ModelConstants.T_IDXS, *plan_rows[:, Plan.ACCELERATION].T)
_assign_xyz(model_packet.orientation, ModelConstants.T_IDXS, *plan_rows[:, Plan.T_FROM_CURRENT_EULER].T)
_assign_xyz(model_packet.orientationRate, ModelConstants.T_IDXS, *plan_rows[:, Plan.ORIENTATION_RATE].T)
_fit_path(driving_packet.path, ModelConstants.POLY_PATH_DEGREE, *plan_rows[:, Plan.POSITION].T)
def _write_temporal_pose(model_packet, outputs: dict[str, np.ndarray]) -> None:
pose_packet = model_packet.temporalPoseDEPRECATED
if "sim_pose" in outputs:
half_width = ModelConstants.POSE_WIDTH // 2
pose_packet.trans = outputs["sim_pose"][0, :half_width].tolist()
pose_packet.transStd = outputs["sim_pose_stds"][0, :half_width].tolist()
pose_packet.rot = outputs["sim_pose"][0, half_width:].tolist()
pose_packet.rotStd = outputs["sim_pose_stds"][0, half_width:].tolist()
return
pose_packet.trans = outputs["plan"][0, 0, Plan.VELOCITY].tolist()
pose_packet.transStd = outputs["plan_stds"][0, 0, Plan.VELOCITY].tolist()
pose_packet.rot = outputs["plan"][0, 0, Plan.ORIENTATION_RATE].tolist()
pose_packet.rotStd = outputs["plan_stds"][0, 0, Plan.ORIENTATION_RATE].tolist()
def _write_lane_family(model_packet, driving_packet, outputs: dict[str, np.ndarray]) -> None:
time_axis = plan_x_idxs_helper(ModelConstants, Plan, outputs)
model_packet.init("laneLines", 4)
for lane_idx in range(4):
lane_builder = model_packet.laneLines[lane_idx]
_assign_xyz(
lane_builder,
time_axis,
np.array(ModelConstants.X_IDXS),
outputs["lane_lines"][0, lane_idx, :, 0],
outputs["lane_lines"][0, lane_idx, :, 1],
)
model_packet.laneLineStds = outputs["lane_lines_stds"][0, :, 0, 0].tolist()
model_packet.laneLineProbs = outputs["lane_lines_prob"][0, 1::2].tolist()
_lane_snapshot(driving_packet.laneLineMeta, model_packet.laneLines, model_packet.laneLineProbs)
model_packet.init("roadEdges", 2)
for edge_idx in range(2):
edge_builder = model_packet.roadEdges[edge_idx]
_assign_xyz(
edge_builder,
time_axis,
np.array(ModelConstants.X_IDXS),
outputs["road_edges"][0, edge_idx, :, 0],
outputs["road_edges"][0, edge_idx, :, 1],
)
model_packet.roadEdgeStds = outputs["road_edges_stds"][0, :, 0, 0].tolist()
def _write_leads(model_packet, outputs: dict[str, np.ndarray]) -> None:
model_packet.init("leadsV3", 3)
for lead_idx in range(3):
lead_builder = model_packet.leadsV3[lead_idx]
_assign_xyva(
lead_builder,
ModelConstants.LEAD_T_IDXS,
*outputs["lead"][0, lead_idx].T,
*outputs["lead_stds"][0, lead_idx].T,
)
lead_builder.prob = outputs["lead_prob"][0, lead_idx].tolist()
lead_builder.probTime = ModelConstants.LEAD_T_OFFSETS[lead_idx]
def _write_meta(model_packet, outputs: dict[str, np.ndarray], memory: DrivePacketMemory, meta_layout, frame_id: int) -> None:
meta = model_packet.meta
meta.desireState = outputs["desire_state"][0].reshape(-1).tolist()
meta.desirePrediction = outputs["desire_pred"][0].reshape(-1).tolist()
meta.engagedProb = outputs["meta"][0, meta_layout.ENGAGED].item()
meta.init("disengagePredictions")
pred = meta.disengagePredictions
pred.t = ModelConstants.META_T_IDXS
pred.brakeDisengageProbs = outputs["meta"][0, meta_layout.BRAKE_DISENGAGE].tolist()
pred.gasDisengageProbs = outputs["meta"][0, meta_layout.GAS_DISENGAGE].tolist()
pred.steerOverrideProbs = outputs["meta"][0, meta_layout.STEER_OVERRIDE].tolist()
pred.brake3MetersPerSecondSquaredProbs = outputs["meta"][0, meta_layout.HARD_BRAKE_3].tolist()
pred.brake4MetersPerSecondSquaredProbs = outputs["meta"][0, meta_layout.HARD_BRAKE_4].tolist()
pred.brake5MetersPerSecondSquaredProbs = outputs["meta"][0, meta_layout.HARD_BRAKE_5].tolist()
if hasattr(meta_layout, "GAS_PRESS") and hasattr(meta_layout, "BRAKE_PRESS"):
pred.gasPressProbs = outputs["meta"][0, meta_layout.GAS_PRESS].tolist()
pred.brakePressProbs = outputs["meta"][0, meta_layout.BRAKE_PRESS].tolist()
meta.hardBrakePredicted = _roll_brake_watch(outputs, memory, meta_layout)
model_packet.confidence = _confidence_bucket(outputs, memory, meta_layout, frame_id)
def populate_drive_messages(primary_msg: capnp._DynamicStructBuilder, extended_msg: capnp._DynamicStructBuilder,
outputs: dict[str, np.ndarray], action: log.ModelDataV2.Action,
memory: DrivePacketMemory, vipc_frame_id: int, vipc_frame_id_extra: int,
frame_id: int, frame_drop: float, timestamp_eof: int,
model_execution_time: float, valid: bool, meta_layout) -> None:
frame_age = frame_id - vipc_frame_id if frame_id > vipc_frame_id else 0
frame_drop_percent = frame_drop * 100
primary_msg.valid = valid
extended_msg.valid = valid
driving_packet = primary_msg.drivingModelData
driving_packet.frameId = vipc_frame_id
driving_packet.frameIdExtra = vipc_frame_id_extra
driving_packet.frameDropPerc = frame_drop_percent
driving_packet.modelExecutionTime = model_execution_time
driving_packet.action = action
model_packet = extended_msg.modelV2
model_packet.frameId = vipc_frame_id
model_packet.frameIdExtra = vipc_frame_id_extra
model_packet.frameAge = frame_age
model_packet.frameDropPerc = frame_drop_percent
model_packet.timestampEof = timestamp_eof
model_packet.modelExecutionTime = model_execution_time
model_packet.action = action
_write_plan_family(model_packet, driving_packet, outputs)
_write_temporal_pose(model_packet, outputs)
_write_lane_family(model_packet, driving_packet, outputs)
_write_leads(model_packet, outputs)
_write_meta(model_packet, outputs, memory, meta_layout, vipc_frame_id)
if SEND_RAW_PRED:
model_packet.rawPredictions = outputs["raw_pred"].tobytes()
def populate_odometry_message(msg: capnp._DynamicStructBuilder, outputs: dict[str, np.ndarray],
vipc_frame_id: int, vipc_dropped_frames: int,
timestamp_eof: int, live_calib_seen: bool) -> None:
msg.valid = live_calib_seen & (vipc_dropped_frames < 1)
odo = msg.cameraOdometry
odo.frameId = vipc_frame_id
odo.timestampEof = timestamp_eof
odo.trans = outputs["pose"][0, :3].tolist()
odo.rot = outputs["pose"][0, 3:].tolist()
odo.wideFromDeviceEuler = outputs["wide_from_device_euler"][0, :].tolist()
odo.roadTransformTrans = outputs["road_transform"][0, :3].tolist()
odo.transStd = outputs["pose_stds"][0, :3].tolist()
odo.rotStd = outputs["pose_stds"][0, 3:].tolist()
odo.wideFromDeviceEulerStd = outputs["wide_from_device_euler_stds"][0, :].tolist()
odo.roadTransformTransStd = outputs["road_transform_stds"][0, :3].tolist()
__all__ = [
"DrivePacketMemory",
"pick_curvature",
"populate_drive_messages",
"populate_odometry_message",
]

View File

@@ -0,0 +1,96 @@
#!/usr/bin/env python3
import codecs
import pathlib
import pickle
import sys
from collections.abc import Iterable
from typing import Any
from cereal import custom
from tinygrad.nn.onnx import OnnxPBParser
from openpilot.iqpilot.selfdrive.iqmodeld.models.helpers import get_active_bundle
from openpilot.iqpilot.selfdrive.iqmodeld.config import Meta
ModelBundle = custom.IQModelManager.ModelBundle
def _blank_proto_doc() -> dict[str, Any]:
return {"graph": {"input": [], "output": []}, "metadata_props": []}
class TelemetryEnvelopeParser(OnnxPBParser):
def _parse_ModelProto(self) -> dict:
envelope = _blank_proto_doc()
for fid, wire_type in self._parse_message(self.reader.len):
if fid == 7:
envelope["graph"] = self._parse_GraphProto()
elif fid == 14:
envelope["metadata_props"].append(self._parse_StringStringEntryProto())
else:
self.reader.skip_field(wire_type)
return envelope
def _shape_fingerprint(value_info: dict[str, Any]) -> tuple[str, tuple[int, ...]]:
resolved = []
for axis in value_info["parsed_type"].shape:
resolved.append(int(axis) if isinstance(axis, int) else 0)
return value_info["name"], tuple(resolved)
def _lookup_metadata(props: Iterable[dict[str, Any]], wanted_key: str) -> str | Any:
for entry in props:
if entry["key"] == wanted_key:
return entry["value"]
return None
class Meta20hz(Meta):
ENGAGED = slice(0, 1)
GAS_DISENGAGE = slice(1, 31, 6)
BRAKE_DISENGAGE = slice(2, 31, 6)
STEER_OVERRIDE = slice(3, 31, 6)
HARD_BRAKE_3 = slice(4, 31, 6)
HARD_BRAKE_4 = slice(5, 31, 6)
HARD_BRAKE_5 = slice(6, 31, 6)
GAS_PRESS = slice(31, 55, 4)
BRAKE_PRESS = slice(32, 55, 4)
LEFT_BLINKER = slice(33, 55, 4)
RIGHT_BLINKER = slice(34, 55, 4)
def select_meta_layout():
active_bundle = get_active_bundle()
return Meta20hz if active_bundle is not None and active_bundle.is20hz else Meta
def _decoded_slices(props: Iterable[dict[str, Any]]):
encoded = _lookup_metadata(props, "output_slices")
assert encoded is not None, "output_slices not found in metadata"
return pickle.loads(codecs.decode(encoded.encode(), "base64"))
def _graph_shape_table(graph_doc: dict[str, Any], field_name: str) -> dict[str, tuple[int, ...]]:
return dict(_shape_fingerprint(item) for item in graph_doc[field_name])
def build_metadata_record(model_path):
parsed = TelemetryEnvelopeParser(model_path).parse()
props = parsed["metadata_props"]
graph = parsed["graph"]
return {
"model_checkpoint": _lookup_metadata(props, "model_checkpoint"),
"output_slices": _decoded_slices(props),
"input_shapes": _graph_shape_table(graph, "input"),
"output_shapes": _graph_shape_table(graph, "output"),
}
if __name__ == "__main__":
model_path = pathlib.Path(sys.argv[1])
metadata_path = model_path.parent / f"{model_path.stem}_metadata.pkl"
with open(metadata_path, "wb") as handle:
pickle.dump(build_metadata_record(model_path), handle)
print(f"saved metadata to {metadata_path}")

View File

@@ -0,0 +1,3 @@
"""
IQ model selection and runner support that is actively used by iqmodeld.
"""

View File

@@ -0,0 +1,100 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
from __future__ import annotations
import os
import re
from pathlib import Path
from openpilot.system.hardware.hw import Paths
_MODEL_ROOT = Path(Paths.model_root())
_OVERRIDE_KEYS = (
"combinedRuntimeArtifact",
"combinedSplitArtifact",
"iqCombinedArtifact",
)
_SPLIT_ROLE_PATTERN = re.compile(r"^driving_(vision|policy|off_policy|on_policy)_(.+)_tinygrad\.pkl$")
def _bundle_models(bundle) -> list:
models = getattr(bundle, "models", None)
return list(models) if models is not None else []
def _bundle_override_map(bundle) -> dict[str, str]:
result: dict[str, str] = {}
for override in getattr(bundle, "overrides", None) or []:
key = getattr(override, "key", None)
value = getattr(override, "value", None)
if key and value:
result[str(key)] = str(value)
return result
def _artifact_name(model) -> str:
return getattr(getattr(model, "artifact", None), "fileName", "") or ""
def _split_suffixes(bundle) -> list[str]:
suffixes: list[str] = []
for model in _bundle_models(bundle):
match = _SPLIT_ROLE_PATTERN.match(_artifact_name(model))
if match:
suffixes.append(match.group(2))
return suffixes
def _derived_candidates(bundle) -> list[str]:
seen: set[str] = set()
candidates: list[str] = []
for suffix in _split_suffixes(bundle):
for candidate in (
f"driving_combined_{suffix}.pkl",
f"iqmodeld_combined_{suffix}.pkl",
):
if candidate not in seen:
seen.add(candidate)
candidates.append(candidate)
ref = getattr(bundle, "ref", None)
if ref:
short_ref = str(ref)[:8]
for candidate in (
f"driving_combined_{short_ref}.pkl",
f"iqmodeld_combined_{short_ref}.pkl",
):
if candidate not in seen:
seen.add(candidate)
candidates.append(candidate)
return candidates
def combined_split_artifact_candidates(bundle) -> list[Path]:
explicit_env = os.getenv("IQMODEL_COMBINED_PKL")
if explicit_env:
explicit_path = Path(explicit_env)
return [explicit_path if explicit_path.is_absolute() else _MODEL_ROOT / explicit_path]
overrides = _bundle_override_map(bundle)
explicit_names = [overrides[key] for key in _OVERRIDE_KEYS if key in overrides]
if explicit_names:
return [_MODEL_ROOT / name for name in explicit_names]
return [_MODEL_ROOT / name for name in _derived_candidates(bundle)]
def resolve_combined_split_artifact(bundle) -> Path | None:
for candidate in combined_split_artifact_candidates(bundle):
if candidate.is_file():
return candidate
return None
def has_combined_split_artifact(bundle) -> bool:
return resolve_combined_split_artifact(bundle) is not None

View File

@@ -0,0 +1,14 @@
#!/usr/bin/env python3
"""
Copyright (c) IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
Public entry point for the model-manifest fetcher: prefers the compiled private
bundle, falling back to the in-tree source. The default-runner fallback lives in
ManifestDecoder now, so no post-import patching is needed.
"""
from openpilot.iqpilot._proprietary_loader import ProprietaryModuleMissing, load_private_module
try:
load_private_module(__name__, "iqpilot_private.models.fetcher")
except ProprietaryModuleMissing:
from iqpilot.models_private_src.fetcher import * # noqa: F403

View File

@@ -0,0 +1,10 @@
#!/usr/bin/env python3
"""
Copyright (c) IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
"""
from openpilot.iqpilot._proprietary_loader import ProprietaryModuleMissing, load_private_module
try:
load_private_module(__name__, "iqpilot_private.models.git_auth")
except ProprietaryModuleMissing:
from iqpilot.models_private_src.git_auth import * # noqa: F403

View File

@@ -0,0 +1,309 @@
#!/usr/bin/env python3
"""
Copyright (c) IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
"""
import json
import os
import shutil
from pathlib import Path
from cereal import custom
from openpilot.common.params import Params
from openpilot.common.swaglog import cloudlog
from openpilot.iqpilot._proprietary_loader import ProprietaryModuleMissing, load_private_module
from openpilot.system.hardware.hw import Paths
try:
load_private_module(__name__, "iqpilot_private.models.helpers")
except ProprietaryModuleMissing:
try:
from iqpilot.models_private_src.helpers import * # noqa: F403
except ImportError:
pass
ModelBundle = custom.IQModelManager.ModelBundle
Runner = custom.IQModelManager.Runner
_MODEL_ROOT = Path(Paths.model_root())
_ACTIVE_BUNDLE_KEY = "ModelManager_ActiveBundle"
_MODELS_CACHE_KEY = "ModelManager_ModelsCache"
_RUNNER_CACHE_KEY = "ModelRunnerTypeCache"
_DOWNLOAD_INDEX_KEY = "ModelManager_DownloadIndex"
_PENDING_MODEL_RESTORE_FILE = "/data/k3_pending_model_restore"
_STOCK_RUNNER = int(Runner.stock)
_TINYGRAD_RUNNER = int(Runner.tinygrad)
_SNPE_RUNNER = int(Runner.snpe)
_DEFAULT_MODEL_DIR = Path(__file__).resolve().parents[1] / "default_model"
_DEFAULT_BUNDLE_JSON = _DEFAULT_MODEL_DIR / "bundle.json"
_DEFAULT_BUNDLE_REF = "default"
def get_default_model_bundle(_bundles):
"""Legacy compatibility hook: stock default is preinstalled, not a manifest bundle."""
return None
def _coerce_runner_value(value) -> int | None:
raw = getattr(value, "raw", value)
try:
return int(raw)
except (TypeError, ValueError):
return None
def _bundle_models(bundle) -> list:
models = getattr(bundle, "models", None)
return list(models) if models is not None else []
def _bundle_needs_runtime_upgrade(bundle) -> bool:
if bundle is None:
return False
if _coerce_runner_value(getattr(bundle, "runner", None)) == _SNPE_RUNNER:
return True
for model in _bundle_models(bundle):
file_name = getattr(getattr(model, "artifact", None), "fileName", "") or ""
if file_name.endswith(".thneed"):
return True
return False
def _load_cached_manifest_bundles(params: Params):
cached = params.get(_MODELS_CACHE_KEY) or {}
bundles = []
for raw_bundle in cached.get("bundles", []):
try:
min_selector_version = int(raw_bundle.get("minimumSelectorVersion", raw_bundle.get("minimum_selector_version", 0)))
compatibility_view = dict(raw_bundle)
compatibility_view["minimumSelectorVersion"] = min_selector_version
is_compatible = globals().get("is_bundle_version_compatible")
if is_compatible is not None and not is_compatible(compatibility_view):
continue
if "short_name" in raw_bundle:
from openpilot.iqpilot.selfdrive.iqmodeld.models.fetcher import ManifestDecoder
bundles.append(ManifestDecoder._decode_bundle(raw_bundle))
continue
if "internalName" in raw_bundle:
bundles.append(ModelBundle(**raw_bundle))
continue
bundle = ModelBundle()
bundle.index = int(raw_bundle["index"])
bundle.internalName = raw_bundle.get("short_name")
bundle.displayName = raw_bundle.get("display_name")
bundle.status = 0
bundle.generation = int(raw_bundle["generation"])
bundle.environment = raw_bundle["environment"]
bundle.runner = raw_bundle.get("runner", Runner.tinygrad)
bundle.is20hz = raw_bundle.get("is_20hz", False)
bundle.minimumSelectorVersion = int(min_selector_version)
bundle.ref = raw_bundle.get("ref")
bundle.overrides = []
for key, value in raw_bundle.get("overrides", {}).items():
override = custom.IQModelManager.Override()
override.key = key
override.value = value
bundle.overrides.append(override)
bundle.models = []
for raw_model in raw_bundle.get("models", []):
model = custom.IQModelManager.Model()
model.type = raw_model.get("type")
for attr_name in ("artifact", "metadata"):
raw_artifact = raw_model.get(attr_name)
if not raw_artifact:
continue
artifact = custom.IQModelManager.Artifact()
artifact.fileName = raw_artifact.get("file_name")
download_uri = custom.IQModelManager.DownloadUri()
download_uri.uri = raw_artifact.get("download_uri", {}).get("url")
download_uri.sha256 = raw_artifact.get("download_uri", {}).get("sha256")
artifact.downloadUri = download_uri
setattr(model, attr_name, artifact)
bundle.models.append(model)
bundles.append(bundle)
except Exception:
continue
return bundles
def _bundle_match_key(bundle) -> tuple[str | None, str | None, str | None]:
return (
getattr(bundle, "ref", None),
getattr(bundle, "internalName", None),
getattr(bundle, "displayName", None),
)
def _find_runtime_upgrade(bundle, params: Params, available_bundles=None):
if not _bundle_needs_runtime_upgrade(bundle):
return bundle
candidate_bundles = available_bundles if available_bundles is not None else _load_cached_manifest_bundles(params)
ref, internal_name, display_name = _bundle_match_key(bundle)
for candidate in candidate_bundles:
if getattr(candidate, "ref", None) and getattr(candidate, "ref", None) == ref:
return candidate
for candidate in candidate_bundles:
if getattr(candidate, "internalName", None) == internal_name:
return candidate
for candidate in candidate_bundles:
if getattr(candidate, "displayName", None) == display_name:
return candidate
return None
def bundle_files_ready(bundle) -> bool:
if bundle is None:
return False
for model in _bundle_models(bundle):
artifact = getattr(model, "artifact", None)
metadata = getattr(model, "metadata", None)
for file_name in (getattr(metadata, "fileName", None), getattr(artifact, "fileName", None)):
if file_name and not (_MODEL_ROOT / file_name).is_file():
return False
return True
def persist_active_bundle(params: Params, bundle) -> None:
params.put(_ACTIVE_BUNDLE_KEY, bundle.to_dict())
params.remove(_RUNNER_CACHE_KEY)
def _load_default_bundle_dict() -> dict:
return json.loads(_DEFAULT_BUNDLE_JSON.read_text())
def _default_bundle_filenames(bundle_dict: dict) -> list[str]:
names = []
for model in bundle_dict.get("models", []):
for artifact in (model.get("metadata"), model.get("artifact")):
file_name = artifact.get("fileName", "") if isinstance(artifact, dict) else ""
if file_name:
names.append(file_name)
return names
def is_default_bundle(bundle) -> bool:
return bool(bundle is not None and getattr(bundle, "ref", None) == _DEFAULT_BUNDLE_REF)
def ensure_default_model_files(bundle_dict: dict = None) -> None:
bundle_dict = bundle_dict if bundle_dict is not None else _load_default_bundle_dict()
try:
_MODEL_ROOT.mkdir(parents=True, exist_ok=True)
except OSError as e:
cloudlog.exception(f"default_model: cannot create model root: {e}")
return
for file_name in _default_bundle_filenames(bundle_dict):
src = _DEFAULT_MODEL_DIR / file_name
dst = _MODEL_ROOT / file_name
if not src.is_file():
cloudlog.error(f"default_model: shipped asset missing {src}")
continue
if dst.is_file() and dst.stat().st_size == src.stat().st_size:
continue
try:
shutil.copy2(src, dst)
cloudlog.warning(f"default_model: staged {file_name} into model root")
except OSError as e:
cloudlog.exception(f"default_model: failed staging {file_name}: {e}")
def select_default_model(params: Params = None) -> None:
params = Params() if params is None else params
bundle_dict = _load_default_bundle_dict()
ensure_default_model_files(bundle_dict)
params.remove(_DOWNLOAD_INDEX_KEY)
params.put(_ACTIVE_BUNDLE_KEY, bundle_dict)
params.remove(_RUNNER_CACHE_KEY)
params.put(_RUNNER_CACHE_KEY, _TINYGRAD_RUNNER)
try:
if os.path.isfile(_PENDING_MODEL_RESTORE_FILE):
os.remove(_PENDING_MODEL_RESTORE_FILE)
except OSError:
pass
def seed_default_bundle_if_unset(params: Params = None) -> None:
params = Params() if params is None else params
if params.get(_ACTIVE_BUNDLE_KEY) or params.get(_DOWNLOAD_INDEX_KEY) is not None:
return
try:
select_default_model(params)
cloudlog.warning("default_model: seeded Default (CD210) as active bundle")
except Exception as e:
cloudlog.exception(f"default_model: failed to seed default bundle: {e}")
def get_runtime_bundle_upgrade(bundle, params: Params = None, available_bundles=None):
params = Params() if params is None else params
return _find_runtime_upgrade(bundle, params, available_bundles)
def get_active_bundle(params: Params = None):
params = Params() if params is None else params
try:
active_bundle = params.get(_ACTIVE_BUNDLE_KEY) or {}
if not active_bundle:
return None
is_compatible = globals().get("is_bundle_version_compatible")
if is_compatible is not None and not is_compatible(active_bundle):
return None
bundle = ModelBundle(**active_bundle)
except Exception:
return None
replacement = _find_runtime_upgrade(bundle, params)
if replacement is not None and replacement is not bundle and bundle_files_ready(replacement):
persist_active_bundle(params, replacement)
return replacement
return bundle
def get_active_model_runner(params: Params = None, force_check=False):
params = Params() if params is None else params
active_bundle = get_active_bundle(params)
if not active_bundle:
seed_default_bundle_if_unset(params)
active_bundle = get_active_bundle(params)
if not active_bundle:
if params.get(_RUNNER_CACHE_KEY) != str(_TINYGRAD_RUNNER):
params.put(_RUNNER_CACHE_KEY, _TINYGRAD_RUNNER)
return _TINYGRAD_RUNNER
cached_runner_type = params.get(_RUNNER_CACHE_KEY)
if cached_runner_type and not force_check and isinstance(cached_runner_type, str) and cached_runner_type.isdigit():
return int(cached_runner_type)
runner_type = _coerce_runner_value(active_bundle.runner)
if runner_type == _SNPE_RUNNER:
replacement = _find_runtime_upgrade(active_bundle, params)
if replacement is not None and replacement is not active_bundle and bundle_files_ready(replacement):
persist_active_bundle(params, replacement)
runner_type = _coerce_runner_value(replacement.runner)
else:
if replacement is not None and getattr(replacement, "index", None) is not None and params.get(_DOWNLOAD_INDEX_KEY) is None:
params.put(_DOWNLOAD_INDEX_KEY, int(replacement.index))
cloudlog.warning(f"Queued tinygrad migration for retired bundle {getattr(active_bundle, 'internalName', '<unknown>')}")
runner_type = _TINYGRAD_RUNNER
if cached_runner_type != runner_type:
params.put(_RUNNER_CACHE_KEY, int(runner_type))
return runner_type

View File

@@ -0,0 +1,13 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
Common base for the per-process inference/runtime states. It seeds the lateral
steer delay from the cached learned value so every subclass starts with a usable
number before its first liveDelay message arrives.
"""
from openpilot.iqpilot.common.steer_delay import cached_steer_delay
class InferenceStateBase:
def __init__(self):
self.lat_delay = cached_steer_delay()

View File

@@ -0,0 +1,391 @@
#!/usr/bin/env python3
"""
Copyright (c) IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
"""
import asyncio
import hashlib
import os
import time
from pathlib import Path
import aiohttp
from cereal import custom
from openpilot.common.realtime import Ratekeeper
from openpilot.common.time_helpers import system_time_valid
from openpilot.iqpilot._proprietary_loader import ProprietaryModuleMissing, load_private_module
from openpilot.common.swaglog import cloudlog
from openpilot.system.hardware.hw import Paths
_TIME_SYNC_WAIT_TIMEOUT_S = 30.0
_TIME_SYNC_POLL_S = 0.5
def _wait_for_valid_clock(timeout: float = _TIME_SYNC_WAIT_TIMEOUT_S) -> None:
if system_time_valid():
return
cloudlog.warning("models_manager: system clock not yet valid, waiting for NTP before fetching")
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
if system_time_valid():
cloudlog.warning("models_manager: system clock is now valid, resuming")
return
time.sleep(_TIME_SYNC_POLL_S)
cloudlog.warning("models_manager: gave up waiting for a valid clock, proceeding anyway")
try:
load_private_module(__name__, "iqpilot_private.models.manager")
_BaseIQModelManager = IQModelManager # noqa: F821
except ProprietaryModuleMissing:
from iqpilot.models_private_src.manager import IQModelManager as _BaseIQModelManager
from openpilot.iqpilot.selfdrive.iqmodeld.models.git_auth import get_aiohttp_auth
from openpilot.iqpilot.selfdrive.iqmodeld.models.helpers import (
bundle_files_ready,
get_active_bundle,
get_runtime_bundle_upgrade,
is_default_bundle,
persist_active_bundle,
)
_ACTIVE_BUNDLE_KEY = "ModelManager_ActiveBundle"
_DOWNLOAD_INDEX_KEY = "ModelManager_DownloadIndex"
_RUNNER_CACHE_KEY = "ModelRunnerTypeCache"
class IQModelManager(_BaseIQModelManager):
def __init__(self):
super().__init__()
self._validated_active_key: tuple[tuple[str, str], ...] | None = None
self._manifest_refresh_key: tuple[tuple[str, str], ...] | None = None
@staticmethod
def _bundle_index(bundle) -> int | None:
try:
return int(getattr(bundle, "index", -1))
except (TypeError, ValueError):
return None
@staticmethod
def _bundle_files(bundle) -> list[tuple[str, str]]:
files = []
for model in getattr(bundle, "models", []) or []:
for artifact in (getattr(model, "metadata", None), getattr(model, "artifact", None)):
filename = getattr(artifact, "fileName", "") if artifact is not None else ""
if not filename:
continue
download_uri = getattr(artifact, "downloadUri", None)
sha256 = getattr(download_uri, "sha256", "") if download_uri is not None else ""
files.append((filename, sha256 or ""))
return files
@staticmethod
def _safe_model_path(filename: str) -> Path | None:
if not filename or os.path.basename(filename) != filename:
cloudlog.warning(f"Ignoring unsafe model filename {filename!r}")
return None
root = Path(Paths.model_root()).resolve()
path = (root / filename).resolve()
try:
path.relative_to(root)
except ValueError:
cloudlog.warning(f"Ignoring model path outside model root {path}")
return None
return path
@staticmethod
def _verify_file_sync(path: Path, expected_hash: str) -> bool:
if not path.is_file():
return False
if not expected_hash:
return True
sha256_hash = hashlib.sha256()
with open(path, "rb") as f:
for chunk in iter(lambda: f.read(1024 * 1024), b""):
sha256_hash.update(chunk)
return sha256_hash.hexdigest().lower() == expected_hash.lower()
def _bundle_validation_key(self, bundle) -> tuple[tuple[str, str], ...]:
return tuple(self._bundle_files(bundle))
def _bundle_files_valid(self, bundle) -> bool:
for filename, expected_hash in self._bundle_files(bundle):
path = self._safe_model_path(filename)
if path is None or not self._verify_file_sync(path, expected_hash):
return False
return True
def _remove_bundle_files(self, bundle) -> None:
for filename, _expected_hash in self._bundle_files(bundle):
path = self._safe_model_path(filename)
if path is None:
continue
for candidate in (path, Path(f"{path}.download")):
try:
if candidate.is_file():
candidate.unlink()
except OSError as e:
cloudlog.exception(f"Failed to remove model artifact {candidate}: {e}")
def _find_available_bundle(self, target):
target_index = self._bundle_index(target)
target_ref = getattr(target, "ref", None)
target_internal = getattr(target, "internalName", None)
target_display = getattr(target, "displayName", None)
for bundle in self.available_models:
if target_index is not None and self._bundle_index(bundle) == target_index:
return bundle
if target_ref and getattr(bundle, "ref", None) == target_ref:
return bundle
if target_internal and getattr(bundle, "internalName", None) == target_internal:
return bundle
if target_display and getattr(bundle, "displayName", None) == target_display:
return bundle
return None
def _bundle_matches(self, left, right) -> bool:
if left is None or right is None:
return False
left_index = self._bundle_index(left)
right_index = self._bundle_index(right)
if left_index is not None and right_index is not None and left_index == right_index:
return True
for attr in ("ref", "internalName", "displayName"):
left_value = getattr(left, attr, None)
if left_value and left_value == getattr(right, attr, None):
return True
return False
def _clear_active_bundle(self) -> None:
self.params.remove(_ACTIVE_BUNDLE_KEY)
self.params.remove(_RUNNER_CACHE_KEY)
self.active_bundle = None
self._validated_active_key = None
def _download_request_matches(self, bundle) -> bool:
bundle_index = self._bundle_index(bundle)
return bundle_index is not None and self._download_index() == bundle_index
def _queue_active_redownload_if_invalid(self) -> None:
if self.active_bundle is None:
self._validated_active_key = None
return
validation_key = self._bundle_validation_key(self.active_bundle)
if validation_key == self._validated_active_key:
return
if self._bundle_files_valid(self.active_bundle):
self._validated_active_key = validation_key
return
bundle = self._find_available_bundle(self.active_bundle) or self.active_bundle
bundle_index = self._bundle_index(bundle)
cloudlog.warning(f"Active model {_display_bundle_name(self.active_bundle)} is missing or corrupt; queueing redownload")
self._remove_bundle_files(bundle)
self._clear_active_bundle()
if bundle_index is not None and self._download_index() is None:
self.params.put(_DOWNLOAD_INDEX_KEY, bundle_index)
def _find_manifest_counterpart(self, target):
# never match by index: indexes shift between manifest generations, and a
# positional match could redownload a different model than the user selected
for attr in ("ref", "internalName", "displayName"):
value = getattr(target, attr, None)
if not value:
continue
for bundle in self.available_models:
if getattr(bundle, attr, None) == value:
return bundle
return None
def _queue_active_manifest_refresh(self) -> None:
active = self.active_bundle
if active is None or is_default_bundle(active):
return
if self._download_index() is not None:
return
counterpart = self._find_manifest_counterpart(active)
if counterpart is None:
return
counterpart_index = self._bundle_index(counterpart)
if counterpart_index is None:
return
active_files = dict(self._bundle_files(active))
stale = False
for filename, sha in self._bundle_files(counterpart):
if not sha:
continue
active_sha = active_files.get(filename)
# an empty recorded hash can't prove a mismatch, so it never triggers a redownload
if active_sha is None or (active_sha and active_sha.lower() != sha.lower()):
stale = True
break
if not stale:
self._manifest_refresh_key = None
return
# the manifest may be an expired offline cache, so keep the active bundle and its
# files in place: the download flow replaces artifacts atomically and only persists
# the counterpart as active once everything landed. One attempt per bundle per run
# so a dead network doesn't turn the 1Hz loop into a download-retry storm.
key = self._bundle_validation_key(active)
if key == self._manifest_refresh_key:
return
self._manifest_refresh_key = key
cloudlog.warning(f"Active model {_display_bundle_name(active)} artifacts are stale vs current manifest; queueing redownload")
self.params.put(_DOWNLOAD_INDEX_KEY, counterpart_index)
async def _download_file(self, url: str, path: str, model) -> None:
temp_path = f"{path}.download"
self._download_start_times[model.fileName] = time.monotonic()
try:
if os.path.exists(temp_path):
os.remove(temp_path)
async with aiohttp.ClientSession(auth=get_aiohttp_auth()) as session:
async with session.get(url) as response:
response.raise_for_status()
total_size = int(response.headers.get("content-length", 0))
bytes_downloaded = 0
with open(temp_path, "wb") as f:
async for chunk in response.content.iter_chunked(self._chunk_size):
f.write(chunk)
bytes_downloaded += len(chunk)
if self._download_index() is None:
raise Exception("Download cancelled")
if total_size > 0:
progress = (bytes_downloaded / total_size) * 100
model.downloadProgress.status = custom.IQModelManager.DownloadStatus.downloading
model.downloadProgress.progress = progress
model.downloadProgress.eta = self._calculate_eta(model.fileName, progress)
self._report_status()
f.flush()
os.fsync(f.fileno())
os.replace(temp_path, path)
except Exception:
if os.path.exists(temp_path):
os.remove(temp_path)
raise
finally:
self._download_start_times.pop(model.fileName, None)
async def _download_bundle(self, model_bundle: custom.IQModelManager.ModelBundle, destination_path: str) -> None:
self.selected_bundle = model_bundle
self.selected_bundle.status = custom.IQModelManager.DownloadStatus.downloading
os.makedirs(destination_path, exist_ok=True)
try:
if not self._download_request_matches(model_bundle):
raise RuntimeError("Download cancelled")
tasks = [self._process_model(model, destination_path) for model in self.selected_bundle.models]
await asyncio.gather(*tasks)
if not self._download_request_matches(model_bundle):
raise RuntimeError("Download cancelled")
self.active_bundle = self.selected_bundle
self.active_bundle.status = custom.IQModelManager.DownloadStatus.downloaded
self.params.put(_ACTIVE_BUNDLE_KEY, self.active_bundle.to_dict())
self.params.remove(_RUNNER_CACHE_KEY)
self.selected_bundle = None
except Exception:
if self._download_request_matches(model_bundle) and self.selected_bundle is not None:
self.selected_bundle.status = custom.IQModelManager.DownloadStatus.failed
else:
self.selected_bundle = None
raise
finally:
self._report_status()
def download(self, model_bundle: custom.IQModelManager.ModelBundle, destination_path: str) -> None:
asyncio.run(self._download_bundle(model_bundle, destination_path))
def _queue_tinygrad_upgrade(self) -> None:
if self.active_bundle is None:
return
replacement = get_runtime_bundle_upgrade(self.active_bundle, self.params, self.available_models)
if replacement is None or replacement is self.active_bundle:
return
if bundle_files_ready(replacement):
persist_active_bundle(self.params, replacement)
self.active_bundle = replacement
return
if self._download_index() is None and getattr(replacement, "index", None) is not None:
self.params.put("ModelManager_DownloadIndex", int(replacement.index))
cloudlog.warning(f"Queued tinygrad upgrade for retired bundle {getattr(self.active_bundle, 'internalName', '<unknown>')}")
def main_thread(self) -> None:
_wait_for_valid_clock()
rk = Ratekeeper(1, print_delay_threshold=None)
while True:
try:
# before NTP the TLS cert reads "not yet valid" and every fetch SSL-fails; one line, not spam
if not system_time_valid():
if not getattr(self, "_ntp_wait_logged", False):
cloudlog.warning("models_manager: waiting for NTP before fetching (system clock not valid)")
self._ntp_wait_logged = True
rk.keep_time()
continue
self._ntp_wait_logged = False
self.available_models = self.model_fetcher.get_available_bundles()
self.active_bundle = get_active_bundle(self.params)
self._queue_active_redownload_if_invalid()
self._queue_tinygrad_upgrade()
self._queue_active_manifest_refresh()
if (index_to_download := self._download_index()) is not None:
if model_to_download := next((model for model in self.available_models if model.index == index_to_download), None):
try:
self.download(model_to_download, Paths.model_root())
except Exception as e:
cloudlog.exception(e)
finally:
self.params.remove("ModelManager_DownloadIndex")
self.selected_bundle = None
if self.params.get("ModelManager_ClearCache"):
self.clear_model_cache()
self.params.remove("ModelManager_ClearCache")
self._report_status()
rk.keep_time()
except Exception as e:
cloudlog.exception(f"Error in main thread: {str(e)}")
rk.keep_time()
def _display_bundle_name(bundle) -> str:
return getattr(bundle, "internalName", None) or getattr(bundle, "displayName", None) or "<unknown>"
def main():
IQModelManager().main_thread()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,3 @@
"""
Runner interfaces used by iqmodeld model execution.
"""

View File

@@ -0,0 +1,207 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
"""
import os
import pickle as _pk
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Any
import numpy as np
from cereal import custom
from openpilot.system.hardware import TICI
from openpilot.system.hardware.hw import Paths as _hw_paths
from openpilot.iqpilot.selfdrive.iqmodeld.models.helpers import get_active_bundle as _fetch_bundle
from openpilot.iqpilot.selfdrive.iqmodeld.models.combined_artifact import has_combined_split_artifact
# ---- runtime type surface (native OpenCL/frame handles resolve to Any off-device) ----
if TYPE_CHECKING:
from openpilot.iqpilot.selfdrive.iqmodeld.native.iqmodel_pyx import GpuMemorySlot, RoadProjector
else:
def _resolve_native_types() -> tuple[Any, Any]:
try:
from openpilot.iqpilot.selfdrive.iqmodeld.native.iqmodel_pyx import GpuMemorySlot as iq_clmem
from openpilot.iqpilot.selfdrive.iqmodeld.native.iqmodel_pyx import RoadProjector as iq_frame
return iq_clmem, iq_frame
except (ModuleNotFoundError, ImportError):
return Any, Any
GpuMemorySlot, RoadProjector = _resolve_native_types()
NumpyDict = dict[str, np.ndarray]
ShapeDict = dict[str, tuple[int, ...]]
SliceDict = dict[str, slice]
CLMemDict = dict[str, GpuMemorySlot]
FrameDict = dict[str, RoadProjector]
ModelType = custom.IQModelManager.Model.Type
Model = custom.IQModelManager.Model
SEND_RAW_PRED = os.getenv("SEND_RAW_PRED")
CUSTOM_MODEL_PATH = _hw_paths.model_root()
_META_FIELDS = ("input_shapes", "output_slices")
USBGPU = "USBGPU" in os.environ
def _configure_accelerator():
"""Point tinygrad at the right backend. Must run before tinygrad is imported,
which is why it fires at module import."""
backend, extra = ("QCOM" if TICI else "CPU"), {}
if USBGPU:
backend, extra = "AMD", {"AMD_IFACE": "USB"}
elif TICI:
extra = {"QCOM_PRIORITY": "8"}
os.environ["DEV"] = backend
os.environ.update(extra)
_configure_accelerator()
def load_artifact_metadata(metadata_filename):
"""Read one artifact's metadata pkl: (input shapes, output slices)."""
with open(os.path.join(CUSTOM_MODEL_PATH, metadata_filename), 'rb') as fh:
blob = _pk.load(fh)
return tuple(blob.get(field, {}) for field in _META_FIELDS)
@dataclass
class ArtifactSpec:
"""One model of the active bundle plus its unpacked metadata."""
model: Any
metadata: Any = None
input_shapes: ShapeDict = field(default_factory=dict)
output_slices: SliceDict = field(default_factory=dict)
def __post_init__(self):
self.metadata = self.model.metadata
if self.metadata:
self.input_shapes, self.output_slices = load_artifact_metadata(self.metadata.fileName)
# kept name: some runners annotate against the old alias
ModelData = ArtifactSpec
class RunnerRoot:
"""Shared root of the runner hierarchy.
Both ModelRunner and the per-model parser mixins (model_types.py) inherit
this, so the concrete `TinygradRunner(ModelRunner, *Tinygrad)` diamond keeps
one consistent parser registry + slice implementation.
"""
parser_method_dict: dict
_model_data: "ArtifactSpec | None"
def _slice_outputs(self, model_outputs):
raise NotImplementedError
class ModelRunner(RunnerRoot):
"""Base for the tinygrad/ONNX runners.
Owns the active bundle's ArtifactSpecs and the shared slice/parse plumbing;
subclasses provide input staging (prepare_inputs) and execution (_run_model).
"""
# False for fused runners, which warp + manage temporal buffers inside the JIT
uses_opencl_warp = True
def __init__(self):
active = _fetch_bundle()
if not active:
raise ValueError("runner started without an active model bundle")
self.models = {spec.type.raw: ArtifactSpec(spec) for spec in active.models}
self.is_20hz_3d = False
self.is_20hz = active.is20hz
self.inputs = {}
self.parser_method_dict = {}
self._model_data = None # active spec for the current operation
self._parser = self._constants = None
def _active_spec(self):
spec = self._model_data
if spec is None:
raise ValueError("Model data is not available. Ensure the model is loaded correctly.")
return spec
# views proxied straight off the active artifact spec; kept out of the class
# body (served via __getattr__) so the read surface stays data-driven
_SPEC_VIEW = frozenset(("input_shapes", "output_slices"))
def __getattr__(self, name):
if name == "constants":
return self._constants
if name == "vision_input_names":
return list(self._active_spec().input_shapes)
if name in ModelRunner._SPEC_VIEW:
return getattr(self._active_spec(), name)
raise AttributeError(name)
def prepare_inputs(self, imgs_cl, numpy_inputs, frames):
"""Stage image + numpy inputs for inference; implemented per backend."""
raise NotImplementedError
def _run_model(self):
"""Execute inference over the staged inputs; implemented per backend."""
raise NotImplementedError
def run_model(self):
# parsing happens inside each backend's _run_model
return self._run_model()
def _slice_outputs(self, model_outputs):
"""Split the flat output vector into named views per the artifact's slice table."""
sliced = {}
for tag, span in self._active_spec().output_slices.items():
sliced[tag] = model_outputs[np.newaxis, span]
if SEND_RAW_PRED:
sliced["raw_pred"] = model_outputs.copy()
return sliced
# ---- runner selection (which backend to build for the active bundle) ----------
def _single_artifact_prefix(bundle, prefix: str) -> bool:
return len(bundle.models) == 1 and bundle.models[0].artifact.fileName.startswith(prefix)
def _is_fused_bundle(bundle) -> bool:
return _single_artifact_prefix(bundle, "driving_fused_")
def _is_supercombo_bundle(bundle) -> bool:
return _single_artifact_prefix(bundle, "driving_supercombo_")
def _is_split_bundle(bundle) -> bool:
present = {m.type.raw for m in bundle.models}
split_kinds = {ModelType.vision, ModelType.policy, ModelType.offPolicy, ModelType.onPolicy}
return not present.isdisjoint(split_kinds)
def get_model_runner() -> "ModelRunner":
"""Build the runner backend that fits the active bundle (supercombo / fused /
combined-split / split / single). Concrete runners are imported lazily so one
backend failing to load can't take down the others at import time."""
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.tinygrad.tinygrad_runner import (TinygradRunner,
TinygradSplitRunner)
bundle = _fetch_bundle()
if not (bundle and bundle.models):
return TinygradRunner(ModelType.supercombo)
if _is_supercombo_bundle(bundle):
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.tinygrad.supercombo_runner import TinygradSupercomboRunner
return TinygradSupercomboRunner()
if _is_fused_bundle(bundle):
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.tinygrad.fused_runner import TinygradFusedRunner
return TinygradFusedRunner()
if _is_split_bundle(bundle) and has_combined_split_artifact(bundle):
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.tinygrad.combined_split_runner import TinygradCombinedSplitRunner
return TinygradCombinedSplitRunner()
if _is_split_bundle(bundle):
return TinygradSplitRunner()
return TinygradRunner(bundle.models[0].type.raw)

View File

@@ -0,0 +1,3 @@
"""
ONNX runner support for iqmodeld.
"""

View File

@@ -0,0 +1,57 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
from __future__ import annotations
import numpy as np
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.model_runner import CLMemDict, FrameDict, ModelType, NumpyDict, ShapeDict
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.model_runner import ModelRunner
from openpilot.iqpilot.selfdrive.iqmodeld import MODEL_PATH
from openpilot.iqpilot.selfdrive.iqmodeld.config import ModelConstants
from openpilot.iqpilot.selfdrive.iqmodeld.parser import ArchiveParser
from openpilot.iqpilot.selfdrive.iqmodeld.runtime.ort import ORT_TYPES_TO_NP_TYPES, make_onnx_cpu_runner
def _onnx_dtype_table(session) -> dict[str, np.dtype]:
return {
tensor_info.name: ORT_TYPES_TO_NP_TYPES[tensor_info.type]
for tensor_info in session.get_inputs()
}
class ONNXRunner(ModelRunner):
def __init__(self):
super().__init__()
self.runner = make_onnx_cpu_runner(MODEL_PATH)
self._constants = ModelConstants
self._model_data = self.models.get(ModelType.supercombo)
self._input_dtypes = _onnx_dtype_table(self.runner)
self._parser = ArchiveParser()
self.parser_method_dict[ModelType.supercombo] = self._parser.parse_outputs
@property
def input_shapes(self) -> ShapeDict:
return {tensor_info.name: tensor_info.shape for tensor_info in self.runner.get_inputs()}
def _frame_as_numpy(self, stream_name: str, imgs_cl: CLMemDict, frames: FrameDict) -> np.ndarray:
flattened = frames[stream_name].as_numpy(imgs_cl[stream_name])
shaped = flattened.reshape(self.input_shapes[stream_name])
return shaped.astype(self._input_dtypes[stream_name])
def prepare_inputs(self, imgs_cl: CLMemDict, numpy_inputs: NumpyDict, frames: FrameDict) -> dict:
staged_inputs = dict(numpy_inputs)
for stream_name in imgs_cl:
staged_inputs[stream_name] = self._frame_as_numpy(stream_name, imgs_cl, frames)
self.inputs = staged_inputs
return staged_inputs
def _parse_outputs(self, model_outputs: np.ndarray) -> NumpyDict:
if self._model_data is None:
raise ValueError("Model data is not available. Ensure the model is loaded correctly.")
return self.parser_method_dict[self._model_data.model.type.raw](self._slice_outputs(model_outputs))
def _run_model(self) -> NumpyDict:
combined = self.runner.run(None, self.inputs)[0].reshape(-1)
return self._parse_outputs(combined)

View File

@@ -0,0 +1,3 @@
"""
Tinygrad runner support for iqmodeld.
"""

View File

@@ -0,0 +1,245 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
from __future__ import annotations
import os
import pickle
from pathlib import Path
from typing import Any
import numpy as np
from openpilot.iqpilot.selfdrive.iqmodeld.models.combined_artifact import resolve_combined_split_artifact
from openpilot.iqpilot.selfdrive.iqmodeld.models.helpers import get_active_bundle
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.model_runner import NumpyDict, ShapeDict, SliceDict
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.model_runner import ModelRunner
from openpilot.iqpilot.selfdrive.iqmodeld.models.split_model_constants import SplitModelConstants
from openpilot.iqpilot.selfdrive.iqmodeld.parser import PhaseParser
def _tinygrad_imports():
from tinygrad.device import Device
from tinygrad.tensor import Tensor
return Tensor, Device
def _phase_roles(meta_by_role: dict[str, dict]) -> list[str]:
return [name for name in meta_by_role if name != "vision"]
def _phase_desire_key(policy_shapes: dict[str, tuple[int, ...]]) -> str:
for key in policy_shapes:
if key.startswith("desire"):
return key
raise KeyError("No desire-like key found in policy inputs")
def _phase_image_keys(vision_shapes: dict[str, tuple[int, ...]]) -> tuple[str, str]:
names = sorted(name for name in vision_shapes if "img" in name)
road_key = next((name for name in names if "big" not in name), None)
wide_key = next((name for name in names if "big" in name), None)
if road_key is None or wide_key is None:
raise ValueError(f"Unable to resolve road/wide image keys from {list(vision_shapes)}")
return road_key, wide_key
def _base_policy_keys(policy_shapes: dict[str, tuple[int, ...]]) -> set[str]:
desired_key = _phase_desire_key(policy_shapes)
return {desired_key, "features_buffer", "traffic_convention", "action_t"}
def _slice_map(raw_blob: np.ndarray, slices: dict[str, slice]) -> NumpyDict:
return {name: raw_blob[np.newaxis, section] for name, section in slices.items() if name != "pad"}
class TinygradCombinedSplitRunner(ModelRunner):
uses_opencl_warp: bool = False
def __init__(self):
super().__init__()
self._constants = SplitModelConstants
self._parser = PhaseParser()
self._bundle = get_active_bundle()
self._artifact_path = resolve_combined_split_artifact(self._bundle)
if self._artifact_path is None:
raise FileNotFoundError("No IQ combined split artifact is available for the active bundle")
with open(self._artifact_path, "rb") as artifact:
runtime_package: dict[Any, Any] = pickle.load(artifact)
self._meta_by_role = runtime_package.get("meta_by_role", runtime_package.get("metadata", {}))
self._policy_roles = runtime_package.get("roles", _phase_roles(self._meta_by_role))
self._camera_programs = {
camera_key: spec
for camera_key, spec in runtime_package.items()
if isinstance(camera_key, tuple) and isinstance(spec, dict)
}
self._execute_bundle = runtime_package.get("execute_bundle", runtime_package.get("run_policy"))
self._frame_stride = int(runtime_package.get("frame_stride", runtime_package.get("frame_skip", 1)))
if "vision" not in self._meta_by_role:
raise ValueError("Combined split artifact is missing vision metadata")
if not self._policy_roles:
raise ValueError("Combined split artifact is missing policy roles")
if self._execute_bundle is None:
raise ValueError("Combined split artifact is missing execute_bundle")
self._vision_meta = self._meta_by_role["vision"]
self._primary_policy_meta = self._meta_by_role[self._policy_roles[0]]
self._desired_key = _phase_desire_key(self._primary_policy_meta["input_shapes"])
self._road_key, self._wide_key = _phase_image_keys(self._vision_meta["input_shapes"])
self._extra_policy_keys = [
key for key in self._primary_policy_meta["input_shapes"]
if key not in _base_policy_keys(self._primary_policy_meta["input_shapes"])
]
self._queue_tensors: dict[str, Any] | None = None
self._numpy_state: dict[str, np.ndarray] | None = None
self._camera_shape: tuple[int, int] | None = None
self._blob_cache: dict[tuple[str, int], Any] = {}
self._last_desire = np.zeros(self._primary_policy_meta["input_shapes"][self._desired_key][2], dtype=np.float32)
@property
def vision_input_names(self) -> list[str]:
return [self._road_key, self._wide_key]
@property
def input_shapes(self) -> ShapeDict:
merged: ShapeDict = dict(self._vision_meta["input_shapes"])
for role in self._policy_roles:
merged.update(self._meta_by_role[role]["input_shapes"])
return merged
@property
def output_slices(self) -> SliceDict:
merged: SliceDict = dict(self._vision_meta["output_slices"])
for role in self._policy_roles:
merged.update(self._meta_by_role[role]["output_slices"])
return merged
def prepare_inputs(self, imgs_cl, numpy_inputs, frames):
raise RuntimeError("Combined split runner manages its own warp + queue state; use run_fused()")
def _frame_blob(self, stream_name: str, buf):
Tensor, Device = _tinygrad_imports()
raw_frame = np.frombuffer(buf.data, dtype=np.uint8)
cache_key = (stream_name, raw_frame.ctypes.data)
tensor = self._blob_cache.get(cache_key)
if tensor is None:
tensor = Tensor.from_blob(raw_frame.ctypes.data, (raw_frame.size,), dtype="uint8", device=Device.DEFAULT)
self._blob_cache[cache_key] = tensor
return tensor
def _allocate_runtime_state(self, camera_width: int, camera_height: int) -> None:
if self._queue_tensors is not None and self._camera_shape == (camera_width, camera_height):
return
if (camera_width, camera_height) not in self._camera_programs:
raise RuntimeError(f"No combined split kernels available for {camera_width}x{camera_height}")
Tensor, Device = _tinygrad_imports()
vision_shapes = self._vision_meta["input_shapes"]
policy_shapes = self._primary_policy_meta["input_shapes"]
image_shape = vision_shapes[self._road_key]
frame_history = image_shape[1] // 6
queue_depth = self._frame_stride * (frame_history - 1) + 1
frame_queue_shape = (queue_depth, 6, image_shape[2], image_shape[3])
feature_shape = policy_shapes["features_buffer"]
desired_shape = policy_shapes[self._desired_key]
traffic_shape = policy_shapes["traffic_convention"]
action_shape = policy_shapes.get("action_t", traffic_shape)
numpy_state = {
"tfm": np.zeros((3, 3), dtype=np.float32),
"big_tfm": np.zeros((3, 3), dtype=np.float32),
"desire": np.zeros(desired_shape[2], dtype=np.float32),
"traffic_convention": np.zeros(traffic_shape, dtype=np.float32),
"action_t": np.zeros(action_shape, dtype=np.float32),
}
for key in self._extra_policy_keys:
numpy_state[key] = np.zeros(policy_shapes[key], dtype=np.float32)
queue_tensors = {
"img_q": Tensor(np.zeros(frame_queue_shape, dtype=np.uint8), device=Device.DEFAULT).contiguous().realize(),
"big_img_q": Tensor(np.zeros(frame_queue_shape, dtype=np.uint8), device=Device.DEFAULT).contiguous().realize(),
"feat_q": Tensor(
np.zeros((self._frame_stride * (feature_shape[1] - 1) + 1, feature_shape[0], feature_shape[2]), dtype=np.float32),
device=Device.DEFAULT,
).contiguous().realize(),
"desire_q": Tensor(
np.zeros((self._frame_stride * desired_shape[1], desired_shape[0], desired_shape[2]), dtype=np.float32),
device=Device.DEFAULT,
).contiguous().realize(),
**{name: Tensor(value, device="NPY").realize() for name, value in numpy_state.items()},
}
self._queue_tensors = queue_tensors
self._numpy_state = numpy_state
self._camera_shape = (camera_width, camera_height)
def _policy_inputs(self) -> dict[str, Any]:
assert self._queue_tensors is not None
tensor_names = ["feat_q", "desire_q", "desire", "traffic_convention", "action_t", *self._extra_policy_keys]
return {name: self._queue_tensors[name] for name in tensor_names if name in self._queue_tensors}
def _merge_policy_outputs(self, raw_outputs: tuple[Any, ...]) -> NumpyDict:
outputs = self._parser.parse_vision_outputs(
_slice_map(raw_outputs[0].numpy().flatten(), self._vision_meta["output_slices"])
)
has_on_policy = any(role == "on_policy" for role in self._policy_roles)
for role_name, tensor_out in zip(self._policy_roles, raw_outputs[1:], strict=True):
parsed = self._parser.parse_policy_outputs(
_slice_map(tensor_out.numpy().flatten(), self._meta_by_role[role_name]["output_slices"])
)
if role_name == "off_policy" and has_on_policy:
parsed.pop("plan", None)
outputs.update(parsed)
if "planplus" in outputs and "plan" in outputs:
outputs["plan"] = outputs["plan"] + outputs["planplus"]
return outputs
def run_fused(self, bufs: dict, transforms: dict[str, np.ndarray], numpy_inputs: NumpyDict) -> NumpyDict:
main_buf = bufs[self._road_key]
self._allocate_runtime_state(main_buf.width, main_buf.height)
assert self._queue_tensors is not None and self._numpy_state is not None and self._camera_shape is not None
self._numpy_state["tfm"][:] = transforms[self._road_key]
self._numpy_state["big_tfm"][:] = transforms[self._wide_key]
current_desire = numpy_inputs[self._desired_key].copy()
current_desire[0] = 0
self._numpy_state["desire"][:] = np.where(current_desire - self._last_desire > 0.99, current_desire, 0)
self._last_desire[:] = current_desire
if "traffic_convention" in numpy_inputs:
self._numpy_state["traffic_convention"][:] = numpy_inputs["traffic_convention"]
if "action_t" in numpy_inputs:
self._numpy_state["action_t"][:] = numpy_inputs["action_t"]
for key in self._extra_policy_keys:
if key in numpy_inputs:
self._numpy_state[key][:] = numpy_inputs[key]
stage_inputs = self._camera_programs[self._camera_shape].get("stage_inputs", self._camera_programs[self._camera_shape].get("warp_enqueue"))
if stage_inputs is None:
raise RuntimeError("Combined split artifact camera entry is missing stage_inputs")
staged_main, staged_wide = stage_inputs(
img_q=self._queue_tensors["img_q"],
big_img_q=self._queue_tensors["big_img_q"],
tfm=self._queue_tensors["tfm"],
big_tfm=self._queue_tensors["big_tfm"],
frame=self._frame_blob(self._road_key, bufs[self._road_key]),
big_frame=self._frame_blob(self._wide_key, bufs[self._wide_key]),
)
raw_outputs = self._execute_bundle(img=staged_main, big_img=staged_wide, **self._policy_inputs())
if not isinstance(raw_outputs, tuple):
raw_outputs = (raw_outputs,)
return self._merge_policy_outputs(raw_outputs)
def _run_model(self) -> NumpyDict:
raise RuntimeError("Combined split runner executes through run_fused()")

View File

@@ -0,0 +1,169 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
from __future__ import annotations
import os
import pickle
from typing import Any
import numpy as np
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.model_runner import (
CUSTOM_MODEL_PATH, NumpyDict, ShapeDict, SliceDict,
)
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.model_runner import ModelRunner
from openpilot.iqpilot.selfdrive.iqmodeld.models.split_model_constants import SplitModelConstants
from openpilot.iqpilot.selfdrive.iqmodeld.parser import PhaseParser
def _tinygrad_imports():
from tinygrad.tensor import Tensor
from tinygrad.device import Device
return Tensor, Device
WARP_DEV = os.getenv('WARP_DEV')
class TinygradFusedRunner(ModelRunner):
"""Runs a fused warp+vision+policy pkl. Bundle ships one `driving_fused_*` artifact."""
uses_opencl_warp: bool = False
def __init__(self):
super().__init__()
self._constants = SplitModelConstants
self._parser = PhaseParser()
if len(self.models) != 1:
raise ValueError(f"fused bundle must have exactly one artifact, got {list(self.models)}")
self._model_data = next(iter(self.models.values()))
pkl_path = os.path.join(CUSTOM_MODEL_PATH, self._model_data.model.artifact.fileName)
with open(pkl_path, 'rb') as f:
self._fused: dict[Any, Any] = pickle.load(f)
self._vision_meta = self._fused['metadata']['vision']
self._on_meta = self._fused['metadata']['on_policy']
self._off_meta = self._fused['metadata']['off_policy']
self._run_policy = self._fused['run_policy']
self._warp_jits: dict[tuple[int, int], Any] = {k: v for k, v in self._fused.items() if isinstance(k, tuple)}
if not self._warp_jits:
raise ValueError("fused pkl has no warp JITs")
self._frame_skip: int = int(self._fused.get('frame_skip', 4))
self._queues: dict[str, Any] | None = None
self._npy_buffers: dict[str, np.ndarray] | None = None
self._cam_resolution: tuple[int, int] | None = None
self._blob_cache: dict[tuple[str, int], Any] = {}
def _frame_tensor(self, key, buf):
Tensor, Device = _tinygrad_imports()
arr = np.frombuffer(buf.data, dtype=np.uint8)
ck = (key, arr.ctypes.data)
t = self._blob_cache.get(ck)
if t is None:
t = Tensor.from_blob(arr.ctypes.data, (arr.size,), dtype='uint8', device=Device.DEFAULT)
self._blob_cache[ck] = t
return t
@property
def vision_input_names(self) -> list[str]:
return ['img', 'big_img']
@property
def input_shapes(self) -> ShapeDict:
return {**self._vision_meta['input_shapes'], **self._on_meta['input_shapes']}
@property
def output_slices(self) -> SliceDict:
merged: SliceDict = {}
for src in (self._vision_meta['output_slices'], self._on_meta['output_slices'], self._off_meta['output_slices']):
merged.update({k: v for k, v in src.items() if k != 'pad'})
return merged
def prepare_inputs(self, imgs_cl, numpy_inputs, frames):
raise RuntimeError("fused runner has no OpenCL path; use run_fused()")
def _ensure_queues(self, cam_w: int, cam_h: int) -> None:
if self._queues is not None and self._cam_resolution == (cam_w, cam_h):
return
if (cam_w, cam_h) not in self._warp_jits:
raise RuntimeError(f"no warp JIT for {cam_w}x{cam_h}; have {sorted(self._warp_jits)}")
Tensor, Device = _tinygrad_imports()
img_shape = self._vision_meta['input_shapes']['img']
fb = self._on_meta['input_shapes']['features_buffer']
dp = self._on_meta['input_shapes']['desire_pulse']
n_frames = img_shape[1] // 6
img_buf_shape = (self._frame_skip * (n_frames - 1) + 1, 6, img_shape[2], img_shape[3])
zeros_u8 = lambda shp: Tensor(np.zeros(shp, dtype=np.uint8), device=Device.DEFAULT).contiguous().realize()
zeros_f32 = lambda shp: Tensor(np.zeros(shp, dtype=np.float32), device=Device.DEFAULT).contiguous().realize()
self._queues = {
'img_q': zeros_u8(img_buf_shape),
'big_img_q': zeros_u8(img_buf_shape),
'feat_q': zeros_f32((self._frame_skip * (fb[1] - 1) + 1, fb[0], fb[2])),
'desire_q': zeros_f32((self._frame_skip * dp[1], dp[0], dp[2])),
}
# shapes must match the captured run_policy JIT inputs
on_shapes = self._on_meta['input_shapes']
self._npy_buffers = {
'desire': np.zeros(dp[2], dtype=np.float32),
'traffic_convention': np.zeros(on_shapes['traffic_convention'], dtype=np.float32),
'action_t': np.zeros(on_shapes['action_t'], dtype=np.float32),
'tfm': np.zeros((3, 3), dtype=np.float32),
'big_tfm': np.zeros((3, 3), dtype=np.float32),
}
self._cam_resolution = (cam_w, cam_h)
def run_fused(self, bufs: dict, transforms: dict[str, np.ndarray], numpy_inputs: NumpyDict) -> NumpyDict:
"""warp + vision + policy in one pass from raw NV12 bufs + transform matrices."""
Tensor, Device = _tinygrad_imports()
main_buf = bufs['img']
self._ensure_queues(main_buf.width, main_buf.height)
assert self._queues is not None and self._npy_buffers is not None
desire_key = next((k for k in numpy_inputs if k.startswith('desire')), None)
if desire_key is not None:
self._npy_buffers['desire'][:] = numpy_inputs[desire_key]
if 'traffic_convention' in numpy_inputs:
self._npy_buffers['traffic_convention'][:] = numpy_inputs['traffic_convention']
if 'action_t' in numpy_inputs:
self._npy_buffers['action_t'][:] = numpy_inputs['action_t']
self._npy_buffers['tfm'][:] = transforms['img']
self._npy_buffers['big_tfm'][:] = transforms['big_img']
npy = lambda key: Tensor(self._npy_buffers[key], device='NPY')
# frames go on the compute device to match the captured warp JIT
frame = self._frame_tensor('img', bufs['img'])
big_frame = self._frame_tensor('big_img', bufs['big_img'])
warp_jit = self._warp_jits[self._cam_resolution]
img, big_img = warp_jit(img_q=self._queues['img_q'], big_img_q=self._queues['big_img_q'],
tfm=npy('tfm'), big_tfm=npy('big_tfm'), frame=frame, big_frame=big_frame)
vision_out_t, on_out_t, off_out_t = self._run_policy(
img=img, big_img=big_img, feat_q=self._queues['feat_q'], desire_q=self._queues['desire_q'],
desire=npy('desire'), traffic_convention=npy('traffic_convention'), action_t=npy('action_t'))
# parse each model's output on its own sliced dict; parsing a merged dict
# would run parse_dynamic_outputs twice and double-parse plan/lead
def _slice(tensor_out, meta) -> NumpyDict:
flat = tensor_out.numpy().flatten()
return {k: flat[np.newaxis, sl] for k, sl in meta['output_slices'].items() if k != 'pad'}
parsed: NumpyDict = {}
parsed.update(self._parser.parse_vision_outputs(_slice(vision_out_t, self._vision_meta)))
parsed.update(self._parser.parse_policy_outputs(_slice(off_out_t, self._off_meta)))
parsed.update(self._parser.parse_policy_outputs(_slice(on_out_t, self._on_meta)))
return parsed
def _run_model(self) -> NumpyDict:
raise RuntimeError("fused path goes through run_fused(), not _run_model()")

View File

@@ -0,0 +1,60 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
from __future__ import annotations
from abc import ABC
from collections.abc import Callable
import numpy as np
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.model_runner import ModelType, NumpyDict
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.model_runner import RunnerRoot
from openpilot.iqpilot.selfdrive.iqmodeld.parser import ArchiveParser, PhaseParser
class _ParserRole(RunnerRoot, ABC):
def _bind_parser_role(self,
selector: int,
parser_builder: Callable[[], object],
projector: Callable[[object, NumpyDict], NumpyDict]) -> None:
parser = parser_builder()
self.parser_method_dict[selector] = lambda model_blob: projector(parser, self._slice_outputs(model_blob))
def _phase_policy(parser: PhaseParser, sliced_outputs: NumpyDict) -> NumpyDict:
return parser.parse_policy_outputs(sliced_outputs)
def _phase_vision(parser: PhaseParser, sliced_outputs: NumpyDict) -> NumpyDict:
return parser.parse_vision_outputs(sliced_outputs)
def _archive_combined(parser: ArchiveParser, sliced_outputs: NumpyDict) -> NumpyDict:
return parser.parse_outputs(sliced_outputs)
class OffPolicyTinygrad(_ParserRole, ABC):
def __init__(self):
self._bind_parser_role(ModelType.offPolicy, PhaseParser, _phase_policy)
class OnPolicyTinygrad(_ParserRole, ABC):
def __init__(self):
self._bind_parser_role(ModelType.onPolicy, PhaseParser, _phase_policy)
class PolicyTinygrad(_ParserRole, ABC):
def __init__(self):
self._bind_parser_role(ModelType.policy, PhaseParser, _phase_policy)
class VisionTinygrad(_ParserRole, ABC):
def __init__(self):
self._bind_parser_role(ModelType.vision, PhaseParser, _phase_vision)
class SupercomboTinygrad(_ParserRole, ABC):
def __init__(self):
self._bind_parser_role(ModelType.supercombo, ArchiveParser, _archive_combined)

View File

@@ -0,0 +1,344 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
from __future__ import annotations
import hashlib
import math
import os
import pickle
import re
from typing import Any
import numpy as np
from openpilot.common.params import Params
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.model_runner import CUSTOM_MODEL_PATH, NumpyDict, ShapeDict, SliceDict
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.model_runner import ModelRunner
from openpilot.iqpilot.selfdrive.iqmodeld.models.split_model_constants import SplitModelConstants
from openpilot.iqpilot.selfdrive.iqmodeld.parser import PhaseParser
def _tinygrad_imports():
from tinygrad.tensor import Tensor
from tinygrad.device import Device
return Tensor, Device
def _captured_queue_depth(warp_jit: Any) -> int | None:
captured = getattr(warp_jit, "captured", None)
infos = getattr(captured, "expected_input_info", None)
if not infos or len(infos) < 2:
return None
view_repr = repr(infos[1][0])
dims = [int(val) for val in re.findall(r"arg=(\d+)", view_repr)]
return dims[0] if len(dims) >= 4 else None
def _captured_devices(warp_jit: Any) -> set[str]:
captured = getattr(warp_jit, "captured", None)
infos = getattr(captured, "expected_input_info", None)
if not infos:
return set()
devices: set[str] = set()
for info in infos:
if isinstance(info, tuple) and len(info) >= 4 and isinstance(info[3], str):
devices.add(info[3])
return devices
def _captured_expected_names(jit_obj: Any) -> list[str]:
captured = getattr(jit_obj, "captured", None)
names = getattr(captured, "expected_names", None)
return list(names) if names else []
def _file_sha256(path: str) -> str:
digest = hashlib.sha256()
with open(path, "rb") as f:
for chunk in iter(lambda: f.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def _is_jit_arg_mismatch(err: BaseException) -> bool:
return "args mismatch in JIT" in str(err)
class TinygradSupercomboRunner(ModelRunner):
"""Runs a single combined supercombo pkl. Bundle ships one `driving_supercombo_*` artifact."""
uses_opencl_warp: bool = False
def __init__(self):
super().__init__()
self._constants = SplitModelConstants
self._parser = PhaseParser()
if len(self.models) != 1:
raise ValueError(f"supercombo bundle must have exactly one artifact, got {list(self.models)}")
self._model_data = next(iter(self.models.values()))
pkl_path = os.path.join(CUSTOM_MODEL_PATH, self._model_data.model.artifact.fileName)
self._pkl_path = pkl_path
self._expected_sha256 = getattr(getattr(self._model_data.model.artifact, "downloadUri", None), "sha256", "") or ""
self._verify_artifact_file()
with open(pkl_path, 'rb') as f:
self._m: dict[Any, Any] = pickle.load(f)
self._meta = self._m['metadata']
self._ish = self._meta['input_shapes']
self._slices = {k: v for k, v in self._meta['output_slices'].items() if k != 'pad'}
self._hidden_slice = self._meta['output_slices']['hidden_state']
self._run_policy = self._m['run_policy']
self._warp_jits: dict[tuple[int, int], Any] = {k: v for k, v in self._m.items() if isinstance(k, tuple)}
if not self._warp_jits:
raise ValueError("supercombo pkl has no warp JITs")
self._frame_skip = int(self._m.get('frame_skip', 4))
self._validate_warp_jits(pkl_path)
self._validate_jit_names()
self._queues: dict[str, Any] | None = None
self._npy: dict[str, np.ndarray] | None = None
self._cam: tuple[int, int] | None = None
self._prev_desire = np.zeros(self._ish['desire_pulse'][2], dtype=np.float32)
self._blob_cache: dict[tuple[str, int], Any] = {}
def _verify_artifact_file(self) -> None:
if not self._expected_sha256:
return
actual_sha256 = _file_sha256(self._pkl_path)
if actual_sha256 == self._expected_sha256:
return
try:
os.remove(self._pkl_path)
except OSError:
pass
redownload_msg = self._schedule_active_bundle_redownload()
raise RuntimeError(
"supercombo artifact SHA mismatch: "
f"expected {self._expected_sha256}, got {actual_sha256} for {self._pkl_path}. "
f"Deleted the stale cached file{redownload_msg}."
)
def _validate_warp_jits(self, pkl_path: str) -> None:
img = self._ish['img']
n_frames = img[1] // 6
expected_depth = self._frame_skip * (n_frames - 1) + 1
expected_device = os.getenv('DEV')
mismatches: list[str] = []
for cam, warp_jit in sorted(self._warp_jits.items()):
captured_depth = _captured_queue_depth(warp_jit)
captured_devices = _captured_devices(warp_jit)
if captured_depth is not None and captured_depth != expected_depth:
mismatches.append(
f"{cam[0]}x{cam[1]} queue-depth captured={captured_depth} expected={expected_depth}"
)
if expected_device and captured_devices and expected_device not in captured_devices:
mismatches.append(
f"{cam[0]}x{cam[1]} device captured={sorted(captured_devices)} expected={expected_device}"
)
if mismatches:
details = "; ".join(mismatches)
raise RuntimeError(
"supercombo warp JIT compatibility mismatch: "
f"{details}. Bundle {pkl_path} was compiled with the wrong backend, frame_skip, or queue shape; "
"re-download or rebuild this model artifact."
)
def _validate_jit_names(self) -> None:
expected_warp_names = ['big_frame', 'big_tfm', 'frame', 'tfm']
expected_policy_names = ['big_img_q', 'desire_q', 'feat_q', 'img_q', 'packed_npy_inputs', 'warped']
mismatches: list[str] = []
policy_names = sorted(_captured_expected_names(self._run_policy))
if policy_names and policy_names != expected_policy_names:
mismatches.append(f"run_policy captured={policy_names} expected={expected_policy_names}")
for cam, warp_jit in sorted(self._warp_jits.items()):
warp_names = sorted(_captured_expected_names(warp_jit))
if warp_names and warp_names != expected_warp_names:
mismatches.append(f"{cam[0]}x{cam[1]} warp captured={warp_names} expected={expected_warp_names}")
if mismatches:
details = "; ".join(mismatches)
actual_sha = None
try:
actual_sha = _file_sha256(self._pkl_path)
except OSError:
pass
if actual_sha and self._expected_sha256 and actual_sha != self._expected_sha256:
try:
os.remove(self._pkl_path)
except OSError:
pass
redownload_msg = self._schedule_active_bundle_redownload()
raise RuntimeError(
"supercombo artifact contract mismatch with stale cached SHA: "
f"{details}. Expected SHA {self._expected_sha256}, got {actual_sha}. "
f"Deleted the stale cached file{redownload_msg}."
)
raise RuntimeError(
"supercombo artifact JIT argument mismatch: "
f"{details}. This model file does not match the current IQPilot runtime contract. "
"Re-download or rebuild this model artifact."
)
def _handle_runtime_jit_mismatch(self, err: BaseException) -> None:
if not _is_jit_arg_mismatch(err):
raise err
actual_sha = None
try:
actual_sha = _file_sha256(self._pkl_path)
except OSError:
pass
if actual_sha and self._expected_sha256 and actual_sha != self._expected_sha256:
try:
os.remove(self._pkl_path)
except OSError:
pass
redownload_msg = self._schedule_active_bundle_redownload()
raise RuntimeError(
"supercombo artifact runtime JIT mismatch with stale cached SHA: "
f"expected {self._expected_sha256}, got {actual_sha} for {self._pkl_path}. "
f"Deleted the stale cached file{redownload_msg}."
) from err
raise RuntimeError(
"supercombo artifact runtime JIT mismatch: "
f"{err}. This model file does not match the current IQPilot runtime contract. "
"Re-download or rebuild this model artifact."
) from err
def _schedule_active_bundle_redownload(self) -> str:
try:
params = Params()
active_bundle = params.get("ModelManager_ActiveBundle") or {}
index = active_bundle.get("index") if isinstance(active_bundle, dict) else None
if isinstance(index, str) and index.isdigit():
index = int(index)
if isinstance(index, int) and index >= 0:
params.put("ModelManager_DownloadIndex", str(index))
params.remove("ModelRunnerTypeCache")
return "; scheduled automatic re-download of the active model"
except Exception:
pass
return "; unable to schedule automatic re-download"
def _frame_tensor(self, key: str, buf):
Tensor, Device = _tinygrad_imports()
arr = np.frombuffer(buf.data, dtype=np.uint8)
ck = (key, arr.ctypes.data)
t = self._blob_cache.get(ck)
if t is None:
t = Tensor.from_blob(arr.ctypes.data, (arr.size,), dtype='uint8', device=Device.DEFAULT)
self._blob_cache[ck] = t
return t
@property
def vision_input_names(self) -> list[str]:
return ['img', 'big_img']
@property
def input_shapes(self) -> ShapeDict:
return dict(self._ish)
@property
def output_slices(self) -> SliceDict:
return dict(self._slices)
def prepare_inputs(self, imgs_cl, numpy_inputs, frames):
raise RuntimeError("supercombo runner has no OpenCL path; use run_fused()")
def _ensure_queues(self, cam_w: int, cam_h: int) -> None:
if self._queues is not None and self._cam == (cam_w, cam_h):
return
if (cam_w, cam_h) not in self._warp_jits:
raise RuntimeError(f"no warp JIT for {cam_w}x{cam_h}; have {sorted(self._warp_jits)}")
Tensor, Device = _tinygrad_imports()
fs = self._frame_skip
img = self._ish['img']
n_frames = img[1] // 6
img_buf = (fs * (n_frames - 1) + 1, 6, img[2], img[3])
fb = self._ish['features_buffer']
dp = self._ish['desire_pulse']
tc = self._ish['traffic_convention']
at = self._ish['action_t']
zeros_u8 = lambda s: Tensor(np.zeros(s, dtype=np.uint8), device=Device.DEFAULT).contiguous().realize()
zeros_f32 = lambda s: Tensor(np.zeros(s, dtype=np.float32), device=Device.DEFAULT).contiguous().realize()
# packed npy block (single NPY tensor, mutated in place via views): order matches run_policy.split
shapes = {'desire': (dp[2],), 'traffic_convention': tuple(tc), 'action_t': tuple(at), 'prev_feat': (fb[0], fb[2])}
sizes = [math.prod(s) for s in shapes.values()]
packed = np.zeros(sum(sizes), dtype=np.float32)
views = {k: v.reshape(s) for (k, s), v in zip(shapes.items(), np.split(packed, np.cumsum(sizes[:-1])), strict=True)}
self._npy = {'tfm': np.zeros((3, 3), dtype=np.float32), 'big_tfm': np.zeros((3, 3), dtype=np.float32), **views}
self._queues = {
'img_q': zeros_u8(img_buf),
'big_img_q': zeros_u8(img_buf),
'feat_q': zeros_f32((fs * fb[1], fb[0], fb[2])),
'desire_q': zeros_f32((fs * dp[1], dp[0], dp[2])),
'tfm': Tensor(self._npy['tfm'], device='NPY'),
'big_tfm': Tensor(self._npy['big_tfm'], device='NPY'),
'packed_npy_inputs': Tensor(packed, device='NPY'),
}
self._cam = (cam_w, cam_h)
def run_fused(self, bufs: dict, transforms: dict[str, np.ndarray], numpy_inputs: NumpyDict) -> NumpyDict:
Tensor, Device = _tinygrad_imports()
main_buf = bufs['img']
self._ensure_queues(main_buf.width, main_buf.height)
assert self._queues is not None and self._npy is not None
self._npy['tfm'][:] = transforms['img']
self._npy['big_tfm'][:] = transforms['big_img']
desire_key = next((k for k in numpy_inputs if k.startswith('desire')), None)
cur = numpy_inputs[desire_key].copy() if desire_key is not None else np.zeros_like(self._prev_desire)
cur[0] = 0
self._npy['desire'][:] = np.where(cur - self._prev_desire > .99, cur, 0)
self._prev_desire[:] = cur
if 'traffic_convention' in numpy_inputs:
self._npy['traffic_convention'][:] = numpy_inputs['traffic_convention']
if 'action_t' in numpy_inputs:
self._npy['action_t'][:] = numpy_inputs['action_t']
# self._npy['prev_feat'] holds last frame's hidden_state (zeros on the first frame)
frame = self._frame_tensor('img', bufs['img'])
big_frame = self._frame_tensor('big_img', bufs['big_img'])
warp = self._warp_jits[self._cam]
try:
warped = warp(tfm=self._queues['tfm'], big_tfm=self._queues['big_tfm'], frame=frame, big_frame=big_frame)
out, = self._run_policy(warped=warped, img_q=self._queues['img_q'], big_img_q=self._queues['big_img_q'],
feat_q=self._queues['feat_q'], desire_q=self._queues['desire_q'],
packed_npy_inputs=self._queues['packed_npy_inputs'])
except Exception as err:
self._handle_runtime_jit_mismatch(err)
raise
flat = out.numpy().flatten()
# feed hidden_state back as prev_feat for the next frame
self._npy['prev_feat'][:] = flat[self._hidden_slice].reshape(self._npy['prev_feat'].shape)
sliced = {k: flat[np.newaxis, sl] for k, sl in self._slices.items()}
return self._parser.parse_vision_outputs(sliced) # single-pass; parse_outputs double-parses a combined dict
def _run_model(self) -> NumpyDict:
raise RuntimeError("supercombo path goes through run_fused(), not _run_model()")

View File

@@ -0,0 +1,186 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
from __future__ import annotations
import pickle
from dataclasses import dataclass
import numpy as np
from tinygrad.tensor import Tensor
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.model_runner import (
CLMemDict,
CUSTOM_MODEL_PATH,
FrameDict,
ModelType,
NumpyDict,
ShapeDict,
SliceDict,
)
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.model_runner import ModelRunner
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.tinygrad.model_types import (
OffPolicyTinygrad,
OnPolicyTinygrad,
PolicyTinygrad,
SupercomboTinygrad,
VisionTinygrad,
)
from openpilot.iqpilot.selfdrive.iqmodeld.models.split_model_constants import SplitModelConstants
from openpilot.iqpilot.selfdrive.iqmodeld.config import ModelConstants
from openpilot.iqpilot.selfdrive.iqmodeld.runtime.tinygrad import qcom_tensor_from_opencl_address
from openpilot.system.hardware import TICI
@dataclass(frozen=True)
class _TensorShapePlan:
dtype: object
device: str
def _artifact_path(filename: str) -> str:
return f"{CUSTOM_MODEL_PATH}/{filename}"
def _load_program_blob(filename: str):
with open(_artifact_path(filename), "rb") as artifact:
try:
return pickle.load(artifact)
except FileNotFoundError as exc:
assert "/dev/kgsl-3d0" not in str(exc), "Model was built on C3 or C3X, but is being loaded on PC"
raise
def _compile_input_plan(captured) -> dict[str, _TensorShapePlan]:
plan: dict[str, _TensorShapePlan] = {}
for name, info in zip(captured.expected_names, captured.expected_input_info, strict=True):
plan[name] = _TensorShapePlan(dtype=info[2], device=info[3])
return plan
def _merge_step_outputs(output_groups: list[NumpyDict]) -> NumpyDict:
stitched: NumpyDict = {}
for payload in output_groups:
stitched.update(payload)
if "planplus" in stitched and "plan" in stitched:
stitched["plan"] = stitched["plan"] + stitched["planplus"]
return stitched
class TinygradRunner(ModelRunner, SupercomboTinygrad, PolicyTinygrad, VisionTinygrad, OffPolicyTinygrad, OnPolicyTinygrad):
def __init__(self, model_type: int = ModelType.supercombo):
ModelRunner.__init__(self)
for initializer in (SupercomboTinygrad, PolicyTinygrad, VisionTinygrad, OffPolicyTinygrad, OnPolicyTinygrad):
initializer.__init__(self)
self._constants = ModelConstants
self._model_data = self.models.get(model_type)
if self._model_data is None or self._model_data.model is None:
raise ValueError(f"Model data for type {model_type} not available.")
asset_name = self._model_data.model.artifact.fileName
assert asset_name.endswith("_tinygrad.pkl"), f"Invalid model file {asset_name} for TinygradRunner"
self.model_run = _load_program_blob(asset_name)
self._input_plan = _compile_input_plan(self.model_run.captured)
self.input_to_dtype = {name: spec.dtype for name, spec in self._input_plan.items()}
self.input_to_device = {name: spec.device for name, spec in self._input_plan.items()}
@property
def vision_input_names(self) -> list[str]:
return [stream_name for stream_name in self.input_shapes if "img" in stream_name]
def _attach_vision_tensor(self, stream_name: str, frame_buffers: CLMemDict, frame_views: FrameDict) -> None:
spec = self._input_plan[stream_name]
frame_buffer = frame_buffers[stream_name]
if TICI:
self.inputs[stream_name] = qcom_tensor_from_opencl_address(frame_buffer.mem_address,
self.input_shapes[stream_name],
dtype=spec.dtype)
return
mirrored = frame_views[stream_name].as_numpy(frame_buffer).reshape(self.input_shapes[stream_name])
self.inputs[stream_name] = Tensor(mirrored, device=spec.device, dtype=spec.dtype).realize()
def _attach_state_tensor(self, tensor_name: str, tensor_value: np.ndarray) -> None:
spec = self._input_plan[tensor_name]
self.inputs[tensor_name] = Tensor(tensor_value, device=spec.device, dtype=spec.dtype).realize()
def prepare_vision_inputs(self, imgs_cl: CLMemDict, frames: FrameDict):
for stream_name in imgs_cl:
if stream_name not in self.inputs or not TICI:
self._attach_vision_tensor(stream_name, imgs_cl, frames)
def prepare_policy_inputs(self, numpy_inputs: NumpyDict):
for tensor_name, tensor_value in numpy_inputs.items():
self._attach_state_tensor(tensor_name, tensor_value)
def prepare_inputs(self, imgs_cl: CLMemDict, numpy_inputs: NumpyDict, frames: FrameDict) -> dict:
self.prepare_vision_inputs(imgs_cl, frames)
self.prepare_policy_inputs(numpy_inputs)
return self.inputs
def _parse_outputs(self, model_outputs: np.ndarray) -> NumpyDict:
if self._model_data is None:
raise ValueError("Model data is not available. Ensure the model is loaded correctly.")
return self.parser_method_dict[self._model_data.model.type.raw](model_outputs)
def _run_model(self) -> NumpyDict:
raw_output = self.model_run(**self.inputs).numpy().reshape(-1)
return self._parse_outputs(raw_output)
class TinygradSplitRunner(ModelRunner):
def __init__(self):
super().__init__()
self.is_20hz_3d = True
self._constants = SplitModelConstants
self.vision_runner = TinygradRunner(ModelType.vision)
self.policy_runner = TinygradRunner(ModelType.policy) if self.models.get(ModelType.policy) else None
self.off_policy_runner = TinygradRunner(ModelType.offPolicy) if self.models.get(ModelType.offPolicy) else None
self.on_policy_runner = TinygradRunner(ModelType.onPolicy) if self.models.get(ModelType.onPolicy) else None
def _policy_units(self) -> list[TinygradRunner]:
return [runner for runner in (self.policy_runner, self.off_policy_runner, self.on_policy_runner) if runner is not None]
def run_vision(self) -> NumpyDict:
return self.vision_runner.run_model()
def run_policy(self) -> NumpyDict:
return _merge_step_outputs([runner.run_model() for runner in self._policy_units()])
def refresh_policy_features(self, features_buffer: np.ndarray) -> None:
for runner in self._policy_units():
if "features_buffer" in runner._input_plan:
runner._attach_state_tensor("features_buffer", features_buffer)
def _run_model(self) -> NumpyDict:
return _merge_step_outputs([self.run_vision(), self.run_policy()])
@property
def vision_input_names(self) -> list[str]:
return list(self.vision_runner.vision_input_names)
@property
def input_shapes(self) -> ShapeDict:
composite: ShapeDict = dict(self.vision_runner.input_shapes)
for runner in self._policy_units():
composite.update(runner.input_shapes)
return composite
@property
def output_slices(self) -> SliceDict:
composite: SliceDict = dict(self.vision_runner.output_slices)
for runner in self._policy_units():
composite.update(runner.output_slices)
return composite
def prepare_inputs(self, imgs_cl: CLMemDict, numpy_inputs: NumpyDict, frames: FrameDict) -> dict:
self.vision_runner.prepare_vision_inputs(imgs_cl, frames)
assembled_inputs = dict(self.vision_runner.inputs)
for runner in self._policy_units():
runner.prepare_policy_inputs(numpy_inputs)
assembled_inputs.update(runner.inputs)
self.inputs = assembled_inputs
return assembled_inputs

View File

@@ -0,0 +1,96 @@
# openpilot model I/O constants (comma.ai, MIT — see LICENSE)
import numpy as np
def index_function(idx, max_val=192, max_idx=32):
return max_val * ((idx/max_idx)**2)
class SplitModelConstants:
# time and distance indices
IDX_N = 33
T_IDXS = [index_function(idx, max_val=10.0) for idx in range(IDX_N)]
X_IDXS = [index_function(idx, max_val=192.0) for idx in range(IDX_N)]
LEAD_T_IDXS = [0., 2., 4., 6., 8., 10.]
LEAD_T_OFFSETS = [0., 2., 4.]
META_T_IDXS = [2., 4., 6., 8., 10.]
# split-model temporal / history run parameters
MODEL_FREQ = 20
HISTORY_FREQ = 5
HISTORY_LEN_SECONDS = 5
TEMPORAL_SKIP = MODEL_FREQ // HISTORY_FREQ
FULL_HISTORY_BUFFER_LEN = MODEL_FREQ * HISTORY_LEN_SECONDS
INPUT_HISTORY_BUFFER_LEN = HISTORY_FREQ * HISTORY_LEN_SECONDS
FEATURE_LEN = 512
DESIRE_LEN = 8
TRAFFIC_CONVENTION_LEN = 2
LAT_PLANNER_STATE_LEN = 4
LATERAL_CONTROL_PARAMS_LEN = 2
PREV_DESIRED_CURV_LEN = 1
# model outputs constants
FCW_THRESHOLDS_5MS2 = np.array([.05, .05, .15, .15, .15], dtype=np.float32)
FCW_THRESHOLDS_3MS2 = np.array([.7, .7], dtype=np.float32)
FCW_5MS2_PROBS_WIDTH = 5
FCW_3MS2_PROBS_WIDTH = 2
DISENGAGE_WIDTH = 5
POSE_WIDTH = 6
WIDE_FROM_DEVICE_WIDTH = 3
LEAD_WIDTH = 4
LANE_LINES_WIDTH = 2
ROAD_EDGES_WIDTH = 2
PLAN_WIDTH = 15
DESIRE_PRED_WIDTH = 8
LAT_PLANNER_SOLUTION_WIDTH = 4
DESIRED_CURV_WIDTH = 1
ACTION_WIDTH = 2
NUM_LANE_LINES = 4
NUM_ROAD_EDGES = 2
LEAD_TRAJ_LEN = 6
DESIRE_PRED_LEN = 4
PLAN_MHP_N = 5
LEAD_MHP_N = 2
PLAN_MHP_SELECTION = 1
LEAD_MHP_SELECTION = 3
FCW_THRESHOLD_5MS2_HIGH = 0.15
FCW_THRESHOLD_5MS2_LOW = 0.05
FCW_THRESHOLD_3MS2 = 0.7
CONFIDENCE_BUFFER_LEN = 5
RYG_GREEN = 0.01165
RYG_YELLOW = 0.06157
POLY_PATH_DEGREE = 4
# model outputs slices
class Plan:
POSITION = slice(0, 3)
VELOCITY = slice(3, 6)
ACCELERATION = slice(6, 9)
T_FROM_CURRENT_EULER = slice(9, 12)
ORIENTATION_RATE = slice(12, 15)
class Meta:
ENGAGED = slice(0, 1)
# next 2, 4, 6, 8, 10 seconds
GAS_DISENGAGE = slice(1, 31, 6)
BRAKE_DISENGAGE = slice(2, 31, 6)
STEER_OVERRIDE = slice(3, 31, 6)
HARD_BRAKE_3 = slice(4, 31, 6)
HARD_BRAKE_4 = slice(5, 31, 6)
HARD_BRAKE_5 = slice(6, 31, 6)
# next 0, 2, 4, 6, 8, 10 seconds
GAS_PRESS = slice(31, 55, 4)
BRAKE_PRESS = slice(32, 55, 4)
LEFT_BLINKER = slice(33, 55, 4)
RIGHT_BLINKER = slice(34, 55, 4)

View File

@@ -0,0 +1,3 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
"""

View File

@@ -0,0 +1,100 @@
/*
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
*/
#include "iqpilot/selfdrive/iqmodeld/native/iqmodel.h"
#include <cstring>
#include "common/clutil.h"
namespace {
void rotate_history_window(cl_command_queue queue, cl_mem timeline_cl, uint8_t history_slots, size_t frame_bytes) {
for (int slot = 0; slot < (history_slots - 1); slot++) {
CL_CHECK(clEnqueueCopyBuffer(queue, timeline_cl, timeline_cl,
(slot + 1) * frame_bytes, slot * frame_bytes,
frame_bytes, 0, nullptr, nullptr));
}
}
} // namespace
FrameCropperBase::FrameCropperBase(cl_device_id device_id, cl_context context) {
work_queue_ = CL_CHECK_ERR(clCreateCommandQueue(context, device_id, 0, &err));
}
FrameCropperBase::~FrameCropperBase() {
CL_CHECK(clReleaseCommandQueue(work_queue_));
}
void FrameCropperBase::configure_planar_tiles(cl_device_id device_id, cl_context context, int output_width, int output_height) {
y_plane_tile_cl_ = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, output_width * output_height, NULL, &err));
u_plane_tile_cl_ = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, (output_width / 2) * (output_height / 2), NULL, &err));
v_plane_tile_cl_ = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, (output_width / 2) * (output_height / 2), NULL, &err));
warp_sampler_init(&sampler_state_, context, device_id);
}
void FrameCropperBase::release_planar_tiles() {
warp_sampler_release(&sampler_state_);
CL_CHECK(clReleaseMemObject(v_plane_tile_cl_));
CL_CHECK(clReleaseMemObject(u_plane_tile_cl_));
CL_CHECK(clReleaseMemObject(y_plane_tile_cl_));
}
void FrameCropperBase::project_frame(cl_mem yuv_cl, int output_width, int output_height,
int frame_width, int frame_height, int frame_stride, int frame_uv_offset,
const mat3 &projection) {
warp_sampler_dispatch(&sampler_state_, work_queue_,
yuv_cl, frame_width, frame_height, frame_stride, frame_uv_offset,
y_plane_tile_cl_, u_plane_tile_cl_, v_plane_tile_cl_,
output_width, output_height, projection);
}
RoadHistoryAssembler::RoadHistoryAssembler(cl_device_id device_id, cl_context context, uint8_t history_slots)
: FrameCropperBase(device_id, context), frame_bytes_(kFrameBytes * sizeof(uint8_t)), history_slots_(history_slots) {
buf_size = kExportBytes;
staging_bytes_ = std::make_unique<uint8_t[]>(buf_size);
publish_pair_cl_ = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, buf_size, NULL, &err));
timeline_cl_ = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, history_slots_ * frame_bytes_, NULL, &err));
latest_region_.origin = (history_slots_ - 1) * frame_bytes_;
latest_region_.size = frame_bytes_;
latest_slot_cl_ = CL_CHECK_ERR(clCreateSubBuffer(timeline_cl_, CL_MEM_READ_WRITE, CL_BUFFER_CREATE_TYPE_REGION, &latest_region_, &err));
packed_frame_kernels_init(&packer_, context, device_id, kOutputWidth, kOutputHeight);
configure_planar_tiles(device_id, context, kOutputWidth, kOutputHeight);
}
cl_mem *RoadHistoryAssembler::project_to_cl(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3 &projection) {
project_frame(yuv_cl, kOutputWidth, kOutputHeight, frame_width, frame_height, frame_stride, frame_uv_offset, projection);
rotate_history_window(work_queue_, timeline_cl_, history_slots_, frame_bytes_);
packed_frame_emit(&packer_, work_queue_, y_plane_tile_cl_, u_plane_tile_cl_, v_plane_tile_cl_, latest_slot_cl_);
packed_frame_clone_range(&packer_, work_queue_, timeline_cl_, publish_pair_cl_, 0, 0, frame_bytes_);
packed_frame_clone_range(&packer_, work_queue_, latest_slot_cl_, publish_pair_cl_, 0, frame_bytes_, frame_bytes_);
clFinish(work_queue_);
return &publish_pair_cl_;
}
RoadHistoryAssembler::~RoadHistoryAssembler() {
release_planar_tiles();
packed_frame_kernels_release(&packer_);
CL_CHECK(clReleaseMemObject(publish_pair_cl_));
CL_CHECK(clReleaseMemObject(timeline_cl_));
CL_CHECK(clReleaseMemObject(latest_slot_cl_));
}
CabinFrameSampler::CabinFrameSampler(cl_device_id device_id, cl_context context) : FrameCropperBase(device_id, context) {
buf_size = kExportBytes;
staging_bytes_ = std::make_unique<uint8_t[]>(buf_size);
configure_planar_tiles(device_id, context, kOutputWidth, kOutputHeight);
}
cl_mem *CabinFrameSampler::project_to_cl(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3 &projection) {
project_frame(yuv_cl, kOutputWidth, kOutputHeight, frame_width, frame_height, frame_stride, frame_uv_offset, projection);
clFinish(work_queue_);
return &y_plane_tile_cl_;
}
CabinFrameSampler::~CabinFrameSampler() {
release_planar_tiles();
}

View File

@@ -0,0 +1,81 @@
/*
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
*/
#pragma once
#include <cassert>
#include <cfloat>
#include <cstdlib>
#include <memory>
#define CL_USE_DEPRECATED_OPENCL_1_2_APIS
#ifdef __APPLE__
#include <OpenCL/cl.h>
#else
#include <CL/cl.h>
#endif
#include "common/mat.h"
#include "iqpilot/selfdrive/iqmodeld/transforms/warp_geometry.h"
#include "iqpilot/selfdrive/iqmodeld/transforms/yuv.h"
class FrameCropperBase {
public:
FrameCropperBase(cl_device_id device_id, cl_context context);
virtual ~FrameCropperBase();
virtual cl_mem *project_to_cl(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3 &projection) = 0;
uint8_t *copy_to_host(cl_mem *source_frames, int buffer_size) {
CL_CHECK(clEnqueueReadBuffer(work_queue_, *source_frames, CL_TRUE, 0, buffer_size, staging_bytes_.get(), 0, nullptr, nullptr));
clFinish(work_queue_);
return &staging_bytes_[0];
}
int buf_size;
protected:
cl_command_queue work_queue_;
std::unique_ptr<uint8_t[]> staging_bytes_;
cl_mem y_plane_tile_cl_;
cl_mem u_plane_tile_cl_;
cl_mem v_plane_tile_cl_;
WarpSamplerState sampler_state_;
void configure_planar_tiles(cl_device_id device_id, cl_context context, int output_width, int output_height);
void release_planar_tiles();
void project_frame(cl_mem yuv_cl, int output_width, int output_height,
int frame_width, int frame_height, int frame_stride, int frame_uv_offset,
const mat3 &projection);
};
class RoadHistoryAssembler : public FrameCropperBase {
public:
RoadHistoryAssembler(cl_device_id device_id, cl_context context, uint8_t history_slots);
~RoadHistoryAssembler() override;
cl_mem *project_to_cl(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3 &projection) override;
static constexpr int kOutputWidth = 512;
static constexpr int kOutputHeight = 256;
static constexpr int kFrameBytes = kOutputWidth * kOutputHeight * 3 / 2;
static constexpr int kExportBytes = kFrameBytes * 2;
private:
PackedFrameKernels packer_;
cl_mem timeline_cl_;
cl_mem latest_slot_cl_;
cl_mem publish_pair_cl_;
cl_buffer_region latest_region_;
size_t frame_bytes_;
uint8_t history_slots_;
};
class CabinFrameSampler : public FrameCropperBase {
public:
CabinFrameSampler(cl_device_id device_id, cl_context context);
~CabinFrameSampler() override;
cl_mem *project_to_cl(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3 &projection) override;
static constexpr int kOutputWidth = 1440;
static constexpr int kOutputHeight = 960;
static constexpr int kExportBytes = kOutputWidth * kOutputHeight;
};

View File

@@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
# distutils: language = c++
# Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
from msgq.visionipc.visionipc cimport cl_device_id, cl_context, cl_mem
cdef extern from "common/mat.h":
cdef struct mat3:
float v[9]
cdef extern from "common/clutil.h":
cdef unsigned long CL_DEVICE_TYPE_DEFAULT
cl_device_id cl_get_device_id(unsigned long)
cl_context cl_create_context(cl_device_id)
void cl_release_context(cl_context)
cdef extern from "iqpilot/selfdrive/iqmodeld/native/iqmodel.h":
cppclass NativeFrameBridge "FrameCropperBase":
int buf_size
unsigned char * copy_to_host(cl_mem*, int);
cl_mem * project_to_cl(cl_mem, int, int, int, int, mat3)
cppclass RoadFrameBridge "RoadHistoryAssembler":
int buf_size
RoadFrameBridge(cl_device_id, cl_context, unsigned char)
cppclass CabinFrameBridge "CabinFrameSampler":
int buf_size
CabinFrameBridge(cl_device_id, cl_context)

View File

@@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
# distutils: language = c++
# Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
from msgq.visionipc.visionipc cimport cl_mem
from msgq.visionipc.visionipc_pyx cimport CLContext as VisionIpcContextBase
cdef class WarpContext(VisionIpcContextBase):
pass
cdef class GpuMemorySlot:
cdef cl_mem * handle_ptr

View File

@@ -0,0 +1,100 @@
# -*- coding: utf-8 -*-
# distutils: language = c++
# cython: c_string_encoding=ascii, language_level=3
# Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
import numpy as np
cimport numpy as cnp
from libc.string cimport memcpy
from libc.stdint cimport uintptr_t
from msgq.visionipc.visionipc cimport cl_mem
from msgq.visionipc.visionipc_pyx cimport VisionBuf, CLContext as VisionIpcContextBase
from .iqmodel cimport CL_DEVICE_TYPE_DEFAULT, cl_get_device_id, cl_create_context, cl_release_context
from .iqmodel cimport mat3, NativeFrameBridge, RoadFrameBridge, CabinFrameBridge
cdef inline mat3 _projection_to_mat3(float[:] values):
cdef mat3 warp_matrix
memcpy(warp_matrix.v, &values[0], 9 * sizeof(float))
return warp_matrix
cdef inline GpuMemorySlot _borrow_cl_slot(void * raw_handle):
cdef GpuMemorySlot carrier = GpuMemorySlot()
carrier.handle_ptr = <cl_mem*>raw_handle
return carrier
cdef inline object _read_u8_view(unsigned char * payload, int length):
return np.asarray(<cnp.uint8_t[:length]> payload)
cdef class WarpContext(VisionIpcContextBase):
def __cinit__(self):
self.device_id = cl_get_device_id(CL_DEVICE_TYPE_DEFAULT)
self.context = cl_create_context(self.device_id)
def __dealloc__(self):
if self.context:
cl_release_context(self.context)
cdef class GpuMemorySlot:
@property
def mem_address(self):
return <uintptr_t>(self.handle_ptr)
def cl_from_visionbuf(VisionBuf buf):
return _borrow_cl_slot(<void*>&buf.buf.buf_cl)
cdef class _ProjectionBridge:
cdef NativeFrameBridge * _native_ptr
cdef int _export_bytes
def __dealloc__(self):
del self._native_ptr
cdef void _attach(self, NativeFrameBridge * native_frame, int export_bytes):
self._native_ptr = native_frame
self._export_bytes = export_bytes
cdef GpuMemorySlot _stage_image(self, VisionBuf buf, float[:] projection):
cdef mat3 projection_spec = _projection_to_mat3(projection)
cdef cl_mem * exported_slot = self._native_ptr.project_to_cl(
buf.buf.buf_cl,
buf.width,
buf.height,
buf.stride,
buf.uv_offset,
projection_spec,
)
return _borrow_cl_slot(exported_slot)
cdef object _export_host_bytes(self, GpuMemorySlot opencl_slot):
cdef unsigned char * payload = self._native_ptr.copy_to_host(opencl_slot.handle_ptr, self._export_bytes)
return _read_u8_view(payload, self._export_bytes)
cdef class FrameProjector(_ProjectionBridge):
def stage(self, VisionBuf buf, float[:] projection):
return self._stage_image(buf, projection)
def as_numpy(self, GpuMemorySlot in_frames):
return self._export_host_bytes(in_frames)
cdef class RoadProjector(FrameProjector):
cdef RoadFrameBridge * _road_ptr
def __cinit__(self, WarpContext context, int buffer_length=2):
self._road_ptr = new RoadFrameBridge(context.device_id, context.context, buffer_length)
self._attach(<NativeFrameBridge*>self._road_ptr, self._road_ptr.buf_size)
cdef class CabinProjector(FrameProjector):
cdef CabinFrameBridge * _cabin_ptr
def __cinit__(self, WarpContext context):
self._cabin_ptr = new CabinFrameBridge(context.device_id, context.context)
self._attach(<NativeFrameBridge*>self._cabin_ptr, self._cabin_ptr.buf_size)

View File

@@ -0,0 +1,219 @@
from __future__ import annotations
from dataclasses import dataclass
import numpy as np
from openpilot.iqpilot.selfdrive.iqmodeld.models.split_model_constants import SplitModelConstants
from openpilot.iqpilot.selfdrive.iqmodeld.config import ModelConstants
def _bounded_exp(values, out=None):
return np.exp(np.clip(values, -np.inf, 11), out=out)
def _sigmoid(values):
return 1.0 / (1.0 + _bounded_exp(-values))
def _softmax_last(values, axis=-1):
values -= np.max(values, axis=axis, keepdims=True)
if values.dtype in (np.float32, np.float64):
_bounded_exp(values, out=values)
else:
values = _bounded_exp(values)
values /= np.sum(values, axis=axis, keepdims=True)
return values
@dataclass(frozen=True)
class _MixtureRecipe:
input_heads: int
output_heads: int
final_shape: tuple[int, ...]
class _TensorKitchen:
def __init__(self, ignore_missing: bool = False):
self.ignore_missing = ignore_missing
def _grab(self, outputs: dict[str, np.ndarray], tensor_name: str) -> np.ndarray | None:
if tensor_name not in outputs:
if not self.ignore_missing:
raise ValueError(f"Missing output {tensor_name}")
return
return outputs[tensor_name]
def categorical(self, outputs: dict[str, np.ndarray], tensor_name: str, shape=None) -> None:
raw = self._grab(outputs, tensor_name)
if raw is None:
return
if shape is not None:
raw = raw.reshape((raw.shape[0],) + shape)
outputs[tensor_name] = _softmax_last(raw, axis=-1)
def binary(self, outputs: dict[str, np.ndarray], tensor_name: str) -> None:
raw = self._grab(outputs, tensor_name)
if raw is None:
return
outputs[tensor_name] = _sigmoid(raw)
def mixture(self, outputs: dict[str, np.ndarray], tensor_name: str, recipe: _MixtureRecipe) -> None:
raw = self._grab(outputs, tensor_name)
if raw is None:
return
reshaped = raw.reshape((raw.shape[0], max(recipe.input_heads, 1), -1))
value_count = (reshaped.shape[2] - recipe.output_heads) // 2
means = reshaped[:, :, :value_count]
stds = _bounded_exp(reshaped[:, :, value_count:2 * value_count])
if recipe.input_heads > 1:
weights = np.zeros((reshaped.shape[0], recipe.input_heads, recipe.output_heads), dtype=reshaped.dtype)
for output_idx in range(recipe.output_heads):
weights[:, :, output_idx - recipe.output_heads] = _softmax_last(
reshaped[:, :, output_idx - recipe.output_heads], axis=-1
)
if recipe.output_heads == 1:
for batch_idx in range(weights.shape[0]):
order = np.argsort(weights[batch_idx][:, 0])[::-1]
weights[batch_idx] = weights[batch_idx][order]
means[batch_idx] = means[batch_idx][order]
stds[batch_idx] = stds[batch_idx][order]
hypothesis_shape = (reshaped.shape[0], recipe.input_heads, *recipe.final_shape)
outputs[f"{tensor_name}_weights"] = weights
outputs[f"{tensor_name}_hypotheses"] = means.reshape(hypothesis_shape)
outputs[f"{tensor_name}_stds_hypotheses"] = stds.reshape(hypothesis_shape)
picked_means = np.zeros((reshaped.shape[0], recipe.output_heads, value_count), dtype=reshaped.dtype)
picked_stds = np.zeros((reshaped.shape[0], recipe.output_heads, value_count), dtype=reshaped.dtype)
for batch_idx in range(weights.shape[0]):
for output_idx in range(recipe.output_heads):
order = np.argsort(weights[batch_idx, :, output_idx])[::-1]
picked_means[batch_idx, output_idx] = means[batch_idx, order[0]]
picked_stds[batch_idx, output_idx] = stds[batch_idx, order[0]]
else:
picked_means = means
picked_stds = stds
final_shape = ((reshaped.shape[0], recipe.output_heads, *recipe.final_shape)
if recipe.output_heads > 1 else (reshaped.shape[0], *recipe.final_shape))
outputs[tensor_name] = picked_means.reshape(final_shape)
outputs[f"{tensor_name}_stds"] = picked_stds.reshape(final_shape)
class ArchiveParser(_TensorKitchen):
def __init__(self, ignore_missing: bool = False):
super().__init__(ignore_missing=ignore_missing)
self._c = ModelConstants
def _recipes(self) -> list[tuple[str, _MixtureRecipe]]:
c = self._c
return [
("plan", _MixtureRecipe(c.PLAN_MHP_N, c.PLAN_MHP_SELECTION, (c.IDX_N, c.PLAN_WIDTH))),
("lane_lines", _MixtureRecipe(0, 0, (c.NUM_LANE_LINES, c.IDX_N, c.LANE_LINES_WIDTH))),
("road_edges", _MixtureRecipe(0, 0, (c.NUM_ROAD_EDGES, c.IDX_N, c.LANE_LINES_WIDTH))),
("pose", _MixtureRecipe(0, 0, (c.POSE_WIDTH,))),
("road_transform", _MixtureRecipe(0, 0, (c.POSE_WIDTH,))),
("wide_from_device_euler", _MixtureRecipe(0, 0, (c.WIDE_FROM_DEVICE_WIDTH,))),
("lead", _MixtureRecipe(c.LEAD_MHP_N, c.LEAD_MHP_SELECTION, (c.LEAD_TRAJ_LEN, c.LEAD_WIDTH))),
]
def parse_outputs(self, outputs: dict[str, np.ndarray]) -> dict[str, np.ndarray]:
c = self._c
for tensor_name, recipe in self._recipes():
self.mixture(outputs, tensor_name, recipe)
if "sim_pose" in outputs:
self.mixture(outputs, "sim_pose", _MixtureRecipe(0, 0, (c.POSE_WIDTH,)))
if "lat_planner_solution" in outputs:
self.mixture(outputs, "lat_planner_solution", _MixtureRecipe(0, 0, (c.IDX_N, c.LAT_PLANNER_SOLUTION_WIDTH)))
if "desired_curvature" in outputs:
self.mixture(outputs, "desired_curvature", _MixtureRecipe(0, 0, (c.DESIRED_CURV_WIDTH,)))
for name in ("lead_prob", "lane_lines_prob", "meta"):
self.binary(outputs, name)
self.categorical(outputs, "desire_state", shape=(c.DESIRE_PRED_WIDTH,))
self.categorical(outputs, "desire_pred", shape=(c.DESIRE_PRED_LEN, c.DESIRE_PRED_WIDTH))
return outputs
class PhaseParser(_TensorKitchen):
def __init__(self, ignore_missing: bool = False):
super().__init__(ignore_missing=ignore_missing)
self._c = SplitModelConstants
def _has_mixture_heads(self, outputs: dict[str, np.ndarray], tensor_name: str, flat_width: int) -> bool:
raw = self._grab(outputs, tensor_name)
if raw is None:
return False
return raw.shape[1] != 2 * flat_width
def _decode_dynamic_family(self, outputs: dict[str, np.ndarray]) -> None:
c = self._c
if "lead" in outputs:
uses_heads = self._has_mixture_heads(outputs, "lead", c.LEAD_MHP_SELECTION * c.LEAD_TRAJ_LEN * c.LEAD_WIDTH)
self.mixture(outputs, "lead", _MixtureRecipe(
c.LEAD_MHP_N if uses_heads else 0,
c.LEAD_MHP_SELECTION if uses_heads else 0,
(c.LEAD_TRAJ_LEN, c.LEAD_WIDTH) if uses_heads else (c.LEAD_MHP_SELECTION, c.LEAD_TRAJ_LEN, c.LEAD_WIDTH),
))
if "plan" in outputs:
uses_heads = self._has_mixture_heads(outputs, "plan", c.IDX_N * c.PLAN_WIDTH)
self.mixture(outputs, "plan", _MixtureRecipe(
c.PLAN_MHP_N if uses_heads else 0,
c.PLAN_MHP_SELECTION if uses_heads else 0,
(c.IDX_N, c.PLAN_WIDTH),
))
if "planplus" in outputs:
self.mixture(outputs, "planplus", _MixtureRecipe(0, 0, (c.IDX_N, c.PLAN_WIDTH)))
def _decode_policy_family(self, outputs: dict[str, np.ndarray]) -> None:
c = self._c
if "action" in outputs:
self.mixture(outputs, "action", _MixtureRecipe(0, 0, (c.ACTION_WIDTH,)))
if "desired_curvature" in outputs:
self.mixture(outputs, "desired_curvature", _MixtureRecipe(0, 0, (c.DESIRED_CURV_WIDTH,)))
if "desire_pred" in outputs:
self.categorical(outputs, "desire_pred", shape=(c.DESIRE_PRED_LEN, c.DESIRE_PRED_WIDTH))
if "desire_state" in outputs:
self.categorical(outputs, "desire_state", shape=(c.DESIRE_PRED_WIDTH,))
if "lane_lines" in outputs:
self.mixture(outputs, "lane_lines", _MixtureRecipe(0, 0, (c.NUM_LANE_LINES, c.IDX_N, c.LANE_LINES_WIDTH)))
if "lane_lines_prob" in outputs:
self.binary(outputs, "lane_lines_prob")
if "lead_prob" in outputs:
self.binary(outputs, "lead_prob")
if "lat_planner_solution" in outputs:
self.mixture(outputs, "lat_planner_solution", _MixtureRecipe(0, 0, (c.IDX_N, c.LAT_PLANNER_SOLUTION_WIDTH)))
if "meta" in outputs:
self.binary(outputs, "meta")
if "road_edges" in outputs:
self.mixture(outputs, "road_edges", _MixtureRecipe(0, 0, (c.NUM_ROAD_EDGES, c.IDX_N, c.LANE_LINES_WIDTH)))
if "sim_pose" in outputs:
self.mixture(outputs, "sim_pose", _MixtureRecipe(0, 0, (c.POSE_WIDTH,)))
def parse_vision_outputs(self, outputs: dict[str, np.ndarray]) -> dict[str, np.ndarray]:
c = self._c
self.mixture(outputs, "pose", _MixtureRecipe(0, 0, (c.POSE_WIDTH,)))
self.mixture(outputs, "wide_from_device_euler", _MixtureRecipe(0, 0, (c.WIDE_FROM_DEVICE_WIDTH,)))
self.mixture(outputs, "road_transform", _MixtureRecipe(0, 0, (c.POSE_WIDTH,)))
self._decode_dynamic_family(outputs)
self._decode_policy_family(outputs)
return outputs
def parse_policy_outputs(self, outputs: dict[str, np.ndarray]) -> dict[str, np.ndarray]:
self._decode_dynamic_family(outputs)
self._decode_policy_family(outputs)
return outputs
def parse_outputs(self, outputs: dict[str, np.ndarray]) -> dict[str, np.ndarray]:
return self.parse_policy_outputs(self.parse_vision_outputs(outputs))
__all__ = [
"ArchiveParser",
"PhaseParser",
]

View File

@@ -0,0 +1,62 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
import itertools
import numpy as np
import onnx
import onnxruntime as ort
ORT_TYPES_TO_NP_TYPES = {
"tensor(float16)": np.float16,
"tensor(float)": np.float32,
"tensor(uint8)": np.uint8,
}
def _promote_raw_half_blob(attribute):
float32_values = np.frombuffer(attribute.raw_data, dtype=np.float16)
attribute.data_type = 1
attribute.raw_data = float32_values.astype(np.float32).tobytes()
def _rewrite_tensor_io_types(model):
for value_info in itertools.chain(model.graph.input, model.graph.output):
if value_info.type.tensor_type.elem_type == 10:
value_info.type.tensor_type.elem_type = 1
def _rewrite_cast_nodes(model):
for node in model.graph.node:
if node.op_type == "Cast" and node.attribute[0].i == 10:
node.attribute[0].i = 1
for attribute in node.attribute:
if hasattr(attribute, "t") and attribute.t.data_type == 10:
_promote_raw_half_blob(attribute.t)
def attributeproto_fp16_to_fp32(attr):
_promote_raw_half_blob(attr)
def convert_fp16_to_fp32(model):
for initializer in model.graph.initializer:
if initializer.data_type == 10:
_promote_raw_half_blob(initializer)
_rewrite_tensor_io_types(model)
_rewrite_cast_nodes(model)
return model.SerializeToString()
def _cpu_session_options():
options = ort.SessionOptions()
options.intra_op_num_threads = 4
options.execution_mode = ort.ExecutionMode.ORT_SEQUENTIAL
options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
return options
def make_onnx_cpu_runner(model_path):
model_blob = convert_fp16_to_fp32(onnx.load(model_path))
return ort.InferenceSession(model_blob, _cpu_session_options(), providers=["CPUExecutionProvider"])

View File

@@ -0,0 +1,23 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
from tinygrad.tensor import Tensor
from tinygrad.helpers import to_mv
_PTR_STRIDE = 8
_RAW_GPU_PTR_SLOT = 20
_RAW_GPU_PTR_VIEW_BYTES = 0x100
def _descriptor_pointer(opencl_address: int) -> int:
return to_mv(opencl_address, _PTR_STRIDE).cast("Q")[0]
def _raw_gpu_pointer(descriptor_pointer: int) -> int:
return to_mv(descriptor_pointer, _RAW_GPU_PTR_VIEW_BYTES).cast("Q")[_RAW_GPU_PTR_SLOT]
def qcom_tensor_from_opencl_address(opencl_address, shape, dtype):
descriptor_pointer = _descriptor_pointer(opencl_address)
device_pointer = _raw_gpu_pointer(descriptor_pointer)
return Tensor.from_blob(device_pointer, shape, dtype=dtype, device="QCOM")

View File

@@ -0,0 +1,96 @@
// Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
// clang++ -O2 repro.cc && ./a.out
#include <sys/types.h>
#include <unistd.h>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <vector>
namespace {
constexpr int kModelWidth = 320;
constexpr int kModelHeight = 640;
constexpr int kRetryWindow = 20;
constexpr int kSlowThresholdMs = 10;
double millis_since_boot() {
timespec stamp{};
#ifdef CLOCK_BOOTTIME
clock_gettime(CLOCK_BOOTTIME, &stamp);
#else
clock_gettime(CLOCK_MONOTONIC, &stamp);
#endif
return stamp.tv_sec * 1000.0 + stamp.tv_nsec * 1e-6;
}
inline float identity_input(uint8_t value) {
return value;
}
void pack_monitoring_tensor(uint8_t *nv12_frame, float *tensor_out) {
const int half_h = kModelHeight / 2;
const int half_w = kModelWidth / 2;
const int plane_area = half_w * half_h;
const int uv_base = kModelWidth * kModelHeight;
for (int row = 0; row < half_h; ++row) {
for (int col = 0; col < half_w; ++col) {
const int slot = col * half_h + row;
const int y_row = row * 2;
const int y_col = col * 2;
tensor_out[slot] = identity_input(nv12_frame[(y_row * kModelWidth) + y_col]);
tensor_out[slot + plane_area] = identity_input(nv12_frame[((y_row + 1) * kModelWidth) + y_col]);
tensor_out[slot + (plane_area * 2)] = identity_input(nv12_frame[(y_row * kModelWidth) + y_col + 1]);
tensor_out[slot + (plane_area * 3)] = identity_input(nv12_frame[((y_row + 1) * kModelWidth) + y_col + 1]);
tensor_out[slot + (plane_area * 4)] = identity_input(nv12_frame[uv_base + (row * half_w) + col]);
tensor_out[slot + (plane_area * 5)] = identity_input(nv12_frame[uv_base + plane_area + (row * half_w) + col]);
}
}
}
double average_runtime_ms(uint8_t *nv12_frame, float *tensor_out) {
double total_ms = 0.0;
for (int i = 0; i < kRetryWindow; ++i) {
const double start_ms = millis_since_boot();
pack_monitoring_tensor(nv12_frame, tensor_out);
total_ms += millis_since_boot() - start_ms;
}
return total_ms / static_cast<double>(kRetryWindow);
}
void dump_stall_trace(uint8_t *nv12_frame, float *tensor_out) {
for (int i = 0; i < 200; ++i) {
const double start_ms = millis_since_boot();
pack_monitoring_tensor(nv12_frame, tensor_out);
printf("%.2f ", millis_since_boot() - start_ms);
}
printf("\n");
}
} // namespace
int main() {
const size_t nv12_bytes = kModelWidth * kModelHeight * 3 / 2;
const size_t tensor_floats = (kModelWidth / 2) * (kModelHeight / 2) * 6;
while (true) {
auto *nv12_frame = static_cast<uint8_t *>(malloc(nv12_bytes));
auto *tensor_out = static_cast<float *>(malloc(tensor_floats * sizeof(float)));
printf("allocate -- %p 0x%zx -- %p 0x%zx\n", nv12_frame, nv12_bytes, tensor_out, tensor_floats * sizeof(float));
const double mean_ms = average_runtime_ms(nv12_frame, tensor_out);
if (mean_ms > kSlowThresholdMs) {
printf("HIT %.2f\n", mean_ms);
printf("BAD\n");
dump_stall_trace(nv12_frame, tensor_out);
return 0;
}
printf("got %.2f\n", mean_ms);
}
}

View File

@@ -0,0 +1,78 @@
from __future__ import annotations
from types import SimpleNamespace
import numpy as np
import pytest
from cereal import log
from openpilot.iqpilot.selfdrive.iqmodeld.config import Plan
from openpilot.iqpilot.selfdrive.iqmodeld.daemon import NeuralEngineState, _merged_plan
import openpilot.iqpilot.selfdrive.iqmodeld.daemon as iqmodeld_daemon
from openpilot.selfdrive.controls.lib.drive_helpers import smooth_value
def _fake_state(**overrides):
base = dict(
PLANPLUS_CONTROL=1.0,
LONG_SMOOTH_SECONDS=0.3,
LAT_SMOOTH_SECONDS=0.1,
MIN_LAT_CONTROL_SPEED=0.3,
mlsim=True,
generation=12,
constants=SimpleNamespace(T_IDXS=np.arange(100), DESIRE_LEN=8),
)
base.update(overrides)
return SimpleNamespace(**base)
@pytest.mark.parametrize(
("control", "vego", "factor"),
[
(0.55, 20.0, 1.0),
(1.0, 25.0, 0.75),
(1.5, 25.1, 0.75),
(2.0, 20.0, 1.0),
],
)
def test_planplus_merge_matches_speed_gate(control: float, vego: float, factor: float):
state = _fake_state(PLANPLUS_CONTROL=control)
base = np.random.rand(1, 100, 15).astype(np.float32)
extra = np.random.rand(1, 100, 15).astype(np.float32)
merged = _merged_plan(state, {"plan": base, "planplus": extra}, vego)
expected = base[0] + (control * factor) * extra[0]
np.testing.assert_allclose(merged, expected, rtol=1e-6, atol=1e-6)
def test_action_dispatch_uses_merged_plan_for_longitudinal_choice(monkeypatch: pytest.MonkeyPatch):
state = _fake_state()
previous = log.ModelDataV2.Action()
recorded_velocity: list[np.ndarray] = []
def fake_accel(plan_vel, plan_accel, t_idxs, action_t=0.0):
recorded_velocity.append(plan_vel.copy())
return 0.0, False
monkeypatch.setattr(iqmodeld_daemon, "get_accel_from_plan", fake_accel)
monkeypatch.setattr(iqmodeld_daemon, "pick_curvature", lambda *args: 0.0)
plan = np.random.rand(1, 100, 15).astype(np.float32)
planplus = np.random.rand(1, 100, 15).astype(np.float32)
outputs = {"plan": plan.copy(), "planplus": planplus.copy()}
NeuralEngineState.get_action_from_model(state, outputs, previous, 0.0, 0.0, 25.0)
expected = plan[0, :, Plan.VELOCITY][:, 0] + 0.75 * planplus[0, :, Plan.VELOCITY][:, 0]
np.testing.assert_allclose(recorded_velocity[0], expected, rtol=1e-5, atol=1e-6)
def test_action_dispatch_honors_direct_action_outputs():
state = _fake_state(mlsim=False, generation=9)
previous = log.ModelDataV2.Action(desiredCurvature=0.0, desiredAcceleration=0.0, shouldStop=False)
outputs = {"action": np.array([[4.0, -0.25]], dtype=np.float32)}
action = NeuralEngineState.get_action_from_model(state, outputs, previous, 0.0, 0.0, 10.0)
expected_accel = smooth_value(-0.25, previous.desiredAcceleration, state.LONG_SMOOTH_SECONDS)
expected_curvature = smooth_value(0.04, previous.desiredCurvature, state.LAT_SMOOTH_SECONDS)
assert action.desiredAcceleration == pytest.approx(expected_accel)
assert action.desiredCurvature == pytest.approx(expected_curvature)
assert action.shouldStop is False

View File

@@ -0,0 +1,184 @@
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
import numpy as np
from openpilot.iqpilot.selfdrive.iqmodeld.models.combined_artifact import resolve_combined_split_artifact
import openpilot.iqpilot.selfdrive.iqmodeld.models.runners.model_runner as runner_helpers
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.tinygrad.combined_split_runner import TinygradCombinedSplitRunner
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.tinygrad import combined_split_runner as combined_runner_mod
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.model_runner import ModelType
from openpilot.iqpilot.selfdrive.iqmodeld.parser import PhaseParser
from openpilot.iqpilot.selfdrive.iqmodeld.tests.test_iqmodeld_contracts import _phase_sample
@dataclass
class _TypeWrap:
raw: int
@dataclass
class _Artifact:
fileName: str
class _Model:
def __init__(self, model_type: int, artifact_name: str):
self.type = _TypeWrap(model_type)
self.artifact = _Artifact(artifact_name)
class _Override:
def __init__(self, key: str, value: str):
self.key = key
self.value = value
class _Bundle:
def __init__(self, models: list[_Model], overrides: list[_Override] | None = None, generation: int = 10):
self.models = models
self.overrides = overrides or []
self.generation = generation
class _FakeTensor:
def __init__(self, values):
self._values = np.asarray(values, dtype=np.float32)
def numpy(self):
return self._values
class _FakeVisionBuf:
width = 1928
height = 1208
data = memoryview(b"\x00" * 64)
def _slice_pack(outputs: dict[str, np.ndarray]) -> tuple[np.ndarray, dict[str, slice]]:
chunks = []
slices: dict[str, slice] = {}
cursor = 0
for name, value in outputs.items():
flat = value.reshape(-1)
slices[name] = slice(cursor, cursor + flat.size)
chunks.append(flat)
cursor += flat.size
return np.concatenate(chunks).astype(np.float32), slices
def test_resolve_combined_split_artifact_prefers_override(tmp_path: Path, monkeypatch):
bundle = _Bundle(
[_Model(ModelType.vision, "driving_vision_demo_tinygrad.pkl"), _Model(ModelType.policy, "driving_policy_demo_tinygrad.pkl")],
overrides=[_Override("combinedRuntimeArtifact", "driving_combined_demo.pkl")],
)
expected = tmp_path / "driving_combined_demo.pkl"
expected.write_bytes(b"iq")
monkeypatch.setattr("openpilot.iqpilot.selfdrive.iqmodeld.models.combined_artifact._MODEL_ROOT", tmp_path)
assert resolve_combined_split_artifact(bundle) == expected
def test_get_model_runner_prefers_combined_split_artifact(monkeypatch):
bundle = _Bundle([
_Model(ModelType.vision, "driving_vision_demo_tinygrad.pkl"),
_Model(ModelType.policy, "driving_policy_demo_tinygrad.pkl"),
], generation=11)
marker = object()
monkeypatch.setattr(runner_helpers, "get_active_bundle", lambda: bundle)
monkeypatch.setattr(runner_helpers, "has_combined_split_artifact", lambda _: True)
monkeypatch.setattr(combined_runner_mod, "TinygradCombinedSplitRunner", lambda: marker)
assert runner_helpers.get_model_runner() is marker
def test_get_model_runner_keeps_split_bundle_on_existing_runner_without_combined_artifact(monkeypatch):
bundle = _Bundle([
_Model(ModelType.vision, "driving_vision_demo_tinygrad.pkl"),
_Model(ModelType.policy, "driving_policy_demo_tinygrad.pkl"),
], generation=12)
marker = object()
monkeypatch.setattr(runner_helpers, "get_active_bundle", lambda: bundle)
monkeypatch.setattr(runner_helpers, "has_combined_split_artifact", lambda _: False)
monkeypatch.setattr(runner_helpers, "TinygradSplitRunner", lambda: marker)
assert runner_helpers.get_model_runner() is marker
def test_combined_split_runner_parses_single_policy_payload(monkeypatch):
vision_raw = _phase_sample(np.random.default_rng(11))
policy_raw = _phase_sample(np.random.default_rng(17))
vision_blob, vision_slices = _slice_pack(vision_raw)
policy_blob, policy_slices = _slice_pack(policy_raw)
runner = TinygradCombinedSplitRunner.__new__(TinygradCombinedSplitRunner)
runner._vision_meta = {
"input_shapes": {"img": (1, 12, 128, 256), "big_img": (1, 12, 128, 256)},
"output_slices": vision_slices,
}
runner._meta_by_role = {
"vision": runner._vision_meta,
"policy": {
"input_shapes": {
"features_buffer": (1, 25, 512),
"desire_pulse": (1, 25, 8),
"traffic_convention": (1, 2),
"action_t": (1, 2),
},
"output_slices": policy_slices,
},
}
runner._policy_roles = ["policy"]
runner._desired_key = "desire_pulse"
runner._road_key = "img"
runner._wide_key = "big_img"
runner._extra_policy_keys = []
runner._queue_tensors = {
"img_q": object(),
"big_img_q": object(),
"feat_q": object(),
"desire_q": object(),
"tfm": object(),
"big_tfm": object(),
"desire": object(),
"traffic_convention": object(),
"action_t": object(),
}
runner._numpy_state = {
"tfm": np.zeros((3, 3), dtype=np.float32),
"big_tfm": np.zeros((3, 3), dtype=np.float32),
"desire": np.zeros(8, dtype=np.float32),
"traffic_convention": np.zeros((1, 2), dtype=np.float32),
"action_t": np.zeros((1, 2), dtype=np.float32),
}
runner._camera_shape = (1928, 1208)
runner._camera_programs = {
(1928, 1208): {"stage_inputs": lambda **kwargs: ("road", "wide")},
}
runner._execute_bundle = lambda **kwargs: (_FakeTensor(vision_blob), _FakeTensor(policy_blob))
runner._parser = PhaseParser()
runner._last_desire = np.zeros(8, dtype=np.float32)
runner._blob_cache = {}
monkeypatch.setattr(TinygradCombinedSplitRunner, "_allocate_runtime_state", lambda self, w, h: None)
monkeypatch.setattr(TinygradCombinedSplitRunner, "_frame_blob", lambda self, name, buf: object())
outputs = runner.run_fused(
{"img": _FakeVisionBuf(), "big_img": _FakeVisionBuf()},
{"img": np.eye(3, dtype=np.float32), "big_img": np.eye(3, dtype=np.float32)},
{
"desire_pulse": np.array([1, 0, 0, 0, 0, 0, 0, 0], dtype=np.float32),
"traffic_convention": np.zeros((1, 2), dtype=np.float32),
"action_t": np.zeros((1, 2), dtype=np.float32),
},
)
assert "pose" in outputs
assert "plan" in outputs
assert outputs["plan"].shape == (1, 33, 15)
assert outputs["action"].shape == (1, 2)

View File

@@ -0,0 +1,44 @@
from __future__ import annotations
import numpy as np
from openpilot.iqpilot.selfdrive.iqmodeld.tools.compile_supercombo import (
_captured_devices,
_captured_queue_depth,
_validate_pose_outputs,
)
class _Captured:
def __init__(self, expected_input_info):
self.expected_input_info = expected_input_info
class _FakeJit:
def __init__(self, expected_input_info):
self.captured = _Captured(expected_input_info)
def test_captured_queue_helpers_extract_depth_and_device():
infos = [
("noop", (), "uchar", "QCOM"),
("reshape(arg=None, src=(noop, stack(arg=None, src=(const(arg=5), const(arg=6), const(arg=128), const(arg=256)))))", (), "uchar", "QCOM"),
("reshape(arg=None, src=(noop, const(arg=3)))", (), "float", "NPY"),
]
fake_jit = _FakeJit(infos)
assert _captured_queue_depth(fake_jit) == 5
assert _captured_devices(fake_jit) == {"QCOM", "NPY"}
def test_validate_pose_outputs_accepts_sane_odometry_payload():
outputs = {
"pose": np.array([[1.0, 0.5, 0.25, 0.1, 0.2, 0.3]], dtype=np.float32),
"pose_stds": np.array([[0.5, 0.4, 0.3, 0.2, 0.2, 0.2]], dtype=np.float32),
"wide_from_device_euler": np.array([[0.1, 0.2, 0.3]], dtype=np.float32),
"wide_from_device_euler_stds": np.array([[0.2, 0.2, 0.2]], dtype=np.float32),
"road_transform": np.array([[0.5, 0.4, 0.3, 0.2, 0.1, 0.0]], dtype=np.float32),
"road_transform_stds": np.array([[0.3, 0.3, 0.3, 0.2, 0.2, 0.2]], dtype=np.float32),
}
_validate_pose_outputs(outputs)

View File

@@ -0,0 +1,123 @@
from __future__ import annotations
import copy
import cereal.messaging as messaging
import numpy as np
from cereal import log
from openpilot.iqpilot.selfdrive.iqmodeld.config import Meta, ModelConstants
from openpilot.iqpilot.selfdrive.iqmodeld.messaging import (
DrivePacketMemory,
pick_curvature,
populate_drive_messages,
populate_odometry_message,
)
from openpilot.iqpilot.selfdrive.iqmodeld.models.split_model_constants import SplitModelConstants
from openpilot.iqpilot.selfdrive.iqmodeld.parser import ArchiveParser, PhaseParser
def _archive_sample(rng: np.random.Generator) -> dict[str, np.ndarray]:
return {
"plan": rng.standard_normal((1, ModelConstants.PLAN_MHP_N * (2 * ModelConstants.IDX_N * ModelConstants.PLAN_WIDTH + ModelConstants.PLAN_MHP_SELECTION)), dtype=np.float32),
"lane_lines": rng.standard_normal((1, 2 * ModelConstants.NUM_LANE_LINES * ModelConstants.IDX_N * ModelConstants.LANE_LINES_WIDTH), dtype=np.float32),
"road_edges": rng.standard_normal((1, 2 * ModelConstants.NUM_ROAD_EDGES * ModelConstants.IDX_N * ModelConstants.LANE_LINES_WIDTH), dtype=np.float32),
"pose": rng.standard_normal((1, 2 * ModelConstants.POSE_WIDTH), dtype=np.float32),
"road_transform": rng.standard_normal((1, 2 * ModelConstants.POSE_WIDTH), dtype=np.float32),
"sim_pose": rng.standard_normal((1, 2 * ModelConstants.POSE_WIDTH), dtype=np.float32),
"wide_from_device_euler": rng.standard_normal((1, 2 * ModelConstants.WIDE_FROM_DEVICE_WIDTH), dtype=np.float32),
"lead": rng.standard_normal((1, ModelConstants.LEAD_MHP_N * (2 * ModelConstants.LEAD_TRAJ_LEN * ModelConstants.LEAD_WIDTH + ModelConstants.LEAD_MHP_SELECTION)), dtype=np.float32),
"lat_planner_solution": rng.standard_normal((1, 2 * ModelConstants.IDX_N * ModelConstants.LAT_PLANNER_SOLUTION_WIDTH), dtype=np.float32),
"desired_curvature": rng.standard_normal((1, 2 * ModelConstants.DESIRED_CURV_WIDTH), dtype=np.float32),
"lead_prob": rng.standard_normal((1, ModelConstants.LEAD_MHP_SELECTION), dtype=np.float32),
"lane_lines_prob": rng.standard_normal((1, ModelConstants.NUM_LANE_LINES * 2), dtype=np.float32),
"meta": rng.standard_normal((1, 55), dtype=np.float32),
"desire_state": rng.standard_normal((1, ModelConstants.DESIRE_PRED_WIDTH), dtype=np.float32),
"desire_pred": rng.standard_normal((1, ModelConstants.DESIRE_PRED_LEN * ModelConstants.DESIRE_PRED_WIDTH), dtype=np.float32),
}
def _phase_sample(rng: np.random.Generator) -> dict[str, np.ndarray]:
c = SplitModelConstants
return {
"pose": rng.standard_normal((1, 2 * c.POSE_WIDTH), dtype=np.float32),
"wide_from_device_euler": rng.standard_normal((1, 2 * c.WIDE_FROM_DEVICE_WIDTH), dtype=np.float32),
"road_transform": rng.standard_normal((1, 2 * c.POSE_WIDTH), dtype=np.float32),
"lead": rng.standard_normal((1, c.LEAD_MHP_N * (2 * c.LEAD_TRAJ_LEN * c.LEAD_WIDTH + c.LEAD_MHP_SELECTION)), dtype=np.float32),
"plan": rng.standard_normal((1, c.PLAN_MHP_N * (2 * c.IDX_N * c.PLAN_WIDTH + c.PLAN_MHP_SELECTION)), dtype=np.float32),
"planplus": rng.standard_normal((1, 2 * c.IDX_N * c.PLAN_WIDTH), dtype=np.float32),
"action": rng.standard_normal((1, 2 * c.ACTION_WIDTH), dtype=np.float32),
"desired_curvature": rng.standard_normal((1, 2 * c.DESIRED_CURV_WIDTH), dtype=np.float32),
"desire_pred": rng.standard_normal((1, c.DESIRE_PRED_LEN * c.DESIRE_PRED_WIDTH), dtype=np.float32),
"desire_state": rng.standard_normal((1, c.DESIRE_PRED_WIDTH), dtype=np.float32),
"lane_lines": rng.standard_normal((1, 2 * c.NUM_LANE_LINES * c.IDX_N * c.LANE_LINES_WIDTH), dtype=np.float32),
"lane_lines_prob": rng.standard_normal((1, c.NUM_LANE_LINES * 2), dtype=np.float32),
"lead_prob": rng.standard_normal((1, c.LEAD_MHP_SELECTION), dtype=np.float32),
"lat_planner_solution": rng.standard_normal((1, 2 * c.IDX_N * c.LAT_PLANNER_SOLUTION_WIDTH), dtype=np.float32),
"meta": rng.standard_normal((1, 55), dtype=np.float32),
"road_edges": rng.standard_normal((1, 2 * c.NUM_ROAD_EDGES * c.IDX_N * c.LANE_LINES_WIDTH), dtype=np.float32),
"sim_pose": rng.standard_normal((1, 2 * c.POSE_WIDTH), dtype=np.float32),
}
def test_archive_parser_contract_snapshot():
outputs = ArchiveParser().parse_outputs(copy.deepcopy(_archive_sample(np.random.default_rng(7))))
assert outputs["plan"].shape == (1, 33, 15)
assert outputs["lane_lines"].shape == (1, 4, 33, 2)
assert outputs["road_edges"].shape == (1, 2, 33, 2)
assert outputs["desire_pred"].shape == (1, 4, 8)
np.testing.assert_allclose(outputs["pose"][0, 0], 0.45617363, rtol=1e-6, atol=1e-6)
np.testing.assert_allclose(outputs["lane_lines_prob"][0, 2], 0.85733712, rtol=1e-6, atol=1e-6)
np.testing.assert_allclose(outputs["desire_state"][0, 0], 0.44964141, rtol=1e-6, atol=1e-6)
np.testing.assert_allclose(outputs["lead_prob"][0, 0], 0.21613698, rtol=1e-6, atol=1e-6)
def test_phase_parser_contract_snapshot():
raw = _phase_sample(np.random.default_rng(23))
outputs = {**PhaseParser().parse_vision_outputs(copy.deepcopy(raw)), **PhaseParser().parse_policy_outputs(copy.deepcopy(raw))}
assert outputs["plan"].shape == (1, 33, 15)
assert outputs["action"].shape == (1, 2)
assert outputs["desired_curvature"].shape == (1, 1)
assert outputs["road_edges"].shape == (1, 2, 33, 2)
np.testing.assert_allclose(outputs["plan"][0, 0, 0], 0.09684439, rtol=1e-6, atol=1e-6)
np.testing.assert_allclose(outputs["action"][0, 0], 0.25458091, rtol=1e-6, atol=1e-6)
np.testing.assert_allclose(outputs["desired_curvature"][0, 0], -0.97072351, rtol=1e-6, atol=1e-6)
np.testing.assert_allclose(outputs["lane_lines_prob"][0, 0], 0.20073657, rtol=1e-6, atol=1e-6)
def test_message_population_contract_snapshot():
raw = _phase_sample(np.random.default_rng(23))
outputs = {**PhaseParser().parse_vision_outputs(copy.deepcopy(raw)), **PhaseParser().parse_policy_outputs(copy.deepcopy(raw))}
action = log.ModelDataV2.Action(desiredCurvature=0.031, desiredAcceleration=-0.12, shouldStop=False)
driving_msg = messaging.new_message("drivingModelData")
model_msg = messaging.new_message("modelV2")
odometry_msg = messaging.new_message("cameraOdometry")
memory = DrivePacketMemory()
populate_drive_messages(
driving_msg, model_msg, outputs, action, memory,
2468, 2470, 2480, 0.05, 123456789, 0.014, True, Meta,
)
populate_odometry_message(odometry_msg, outputs, 2468, 0, 123456789, True)
np.testing.assert_allclose(driving_msg.drivingModelData.laneLineMeta.leftY, -0.21672775, rtol=1e-6, atol=1e-6)
np.testing.assert_allclose(model_msg.modelV2.meta.engagedProb, 0.64853197, rtol=1e-6, atol=1e-6)
np.testing.assert_allclose(odometry_msg.cameraOdometry.trans[0], 0.0360266, rtol=1e-6, atol=1e-6)
assert int(model_msg.modelV2.confidence.raw) == 2
def test_curvature_selection_contract_snapshot():
raw = _phase_sample(np.random.default_rng(23))
outputs = {**PhaseParser().parse_vision_outputs(copy.deepcopy(raw)), **PhaseParser().parse_policy_outputs(copy.deepcopy(raw))}
plan_rows = outputs["plan"][0]
direct = pick_curvature(outputs, plan_rows, 27.5, 0.8, synthetic_lane_logic=False)
fallback = pick_curvature(outputs, plan_rows, 27.5, 0.8, synthetic_lane_logic=True)
np.testing.assert_allclose(direct, -0.97072351, rtol=1e-6, atol=1e-6)
np.testing.assert_allclose(fallback, -0.0689389, rtol=1e-6, atol=1e-6)

View File

@@ -0,0 +1,150 @@
"""
Copyright (c) IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
"""
from dataclasses import dataclass, field
from openpilot.iqpilot.selfdrive.iqmodeld.models.manager import IQModelManager, _DOWNLOAD_INDEX_KEY
@dataclass
class _DownloadUri:
sha256: str = ""
uri: str = ""
@dataclass
class _Artifact:
fileName: str = ""
downloadUri: _DownloadUri = field(default_factory=_DownloadUri)
@dataclass
class _Model:
artifact: _Artifact = field(default_factory=_Artifact)
metadata: _Artifact | None = None
@dataclass
class _Bundle:
index: int = 0
ref: str = ""
internalName: str = ""
displayName: str = ""
models: list = field(default_factory=list)
class _FakeParams:
def __init__(self):
self.store = {}
def get(self, key):
return self.store.get(key)
def put(self, key, value):
self.store[key] = value
def remove(self, key):
self.store.pop(key, None)
def _bundle(index, name, sha, filename="driving_vision_test_tinygrad.pkl"):
return _Bundle(
index=index,
ref=f"ref-{name}",
internalName=name,
displayName=f"{name} display",
models=[_Model(artifact=_Artifact(fileName=filename, downloadUri=_DownloadUri(sha256=sha)))],
)
def _manager(active, available):
mgr = IQModelManager.__new__(IQModelManager)
mgr.params = _FakeParams()
mgr.active_bundle = active
mgr.available_models = available
mgr._validated_active_key = None
mgr._manifest_refresh_key = None
return mgr
def test_stale_active_bundle_queues_redownload_at_current_index():
active = _bundle(55, "WMIV12", "a" * 64)
counterpart = _bundle(12, "WMIV12", "b" * 64)
mgr = _manager(active, [counterpart])
mgr._queue_active_manifest_refresh()
assert mgr.params.get(_DOWNLOAD_INDEX_KEY) == 12
assert mgr.active_bundle is active
def test_matching_shas_do_not_queue():
active = _bundle(55, "WMIV12", "a" * 64)
counterpart = _bundle(12, "WMIV12", "A" * 64)
mgr = _manager(active, [counterpart])
mgr._queue_active_manifest_refresh()
assert mgr.params.get(_DOWNLOAD_INDEX_KEY) is None
def test_retired_bundle_is_left_alone():
active = _bundle(55, "WMIV12", "a" * 64)
mgr = _manager(active, [_bundle(12, "OtherModel", "b" * 64)])
mgr._queue_active_manifest_refresh()
assert mgr.params.get(_DOWNLOAD_INDEX_KEY) is None
assert mgr.active_bundle is active
def test_default_bundle_is_never_refreshed():
active = _bundle(0, "Default", "a" * 64)
active.ref = "default"
mgr = _manager(active, [_bundle(0, "Default", "b" * 64)])
mgr._queue_active_manifest_refresh()
assert mgr.params.get(_DOWNLOAD_INDEX_KEY) is None
def test_pending_download_blocks_refresh():
active = _bundle(55, "WMIV12", "a" * 64)
mgr = _manager(active, [_bundle(12, "WMIV12", "b" * 64)])
mgr.params.put(_DOWNLOAD_INDEX_KEY, 3)
mgr._queue_active_manifest_refresh()
assert mgr.params.get(_DOWNLOAD_INDEX_KEY) == 3
def test_empty_manifest_hash_never_triggers():
active = _bundle(55, "WMIV12", "a" * 64)
mgr = _manager(active, [_bundle(12, "WMIV12", "")])
mgr._queue_active_manifest_refresh()
assert mgr.params.get(_DOWNLOAD_INDEX_KEY) is None
def test_refresh_queued_once_per_run():
active = _bundle(55, "WMIV12", "a" * 64)
mgr = _manager(active, [_bundle(12, "WMIV12", "b" * 64)])
mgr._queue_active_manifest_refresh()
assert mgr.params.get(_DOWNLOAD_INDEX_KEY) == 12
mgr.params.remove(_DOWNLOAD_INDEX_KEY)
mgr._queue_active_manifest_refresh()
assert mgr.params.get(_DOWNLOAD_INDEX_KEY) is None
def test_counterpart_matched_by_name_not_index():
active = _bundle(55, "WMIV12", "a" * 64)
imposter = _bundle(55, "OtherModel", "c" * 64)
counterpart = _bundle(12, "WMIV12", "b" * 64)
mgr = _manager(active, [imposter, counterpart])
mgr._queue_active_manifest_refresh()
assert mgr.params.get(_DOWNLOAD_INDEX_KEY) == 12

View File

@@ -0,0 +1,73 @@
from __future__ import annotations
import os
from dataclasses import dataclass
from pathlib import Path
import numpy as np
from tinygrad.tensor import Tensor
import openpilot.iqpilot.selfdrive.iqmodeld.models.helpers as bundle_helpers
import openpilot.iqpilot.selfdrive.iqmodeld.models.runners.model_runner as model_runner_mod
import openpilot.iqpilot.selfdrive.iqmodeld.models.runners.tinygrad.tinygrad_runner as tinygrad_runner_mod
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.model_runner import ModelType
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.tinygrad.tinygrad_runner import TinygradRunner
LOCAL_MODEL_DIR = Path(__file__).resolve().parents[1] / "default_model"
@dataclass
class _TypeWrap:
raw: int
@dataclass
class _Artifact:
fileName: str
class _Model:
def __init__(self, model_type: int, artifact_name: str, metadata_name: str):
self.type = _TypeWrap(model_type)
self.artifact = _Artifact(artifact_name)
self.metadata = _Artifact(metadata_name)
class _Bundle:
def __init__(self, models: list[_Model], is_20hz: bool = False):
self.models = models
self.is20hz = is_20hz
def _seed_runner_inputs(runner: TinygradRunner) -> None:
for name, shape in runner.input_shapes.items():
runner.inputs[name] = Tensor(
np.zeros(shape, dtype=np.float32),
device=runner.input_to_device[name],
dtype=runner.input_to_dtype[name],
).realize()
def test_local_tinygrad_models_execute(monkeypatch):
bundle = _Bundle([
_Model(ModelType.vision, "driving_vision_c210m_tinygrad.pkl", "driving_vision_c210m_metadata.pkl"),
_Model(ModelType.policy, "driving_policy_c210m_tinygrad.pkl", "driving_policy_c210m_metadata.pkl"),
])
monkeypatch.setattr(bundle_helpers, "get_active_bundle", lambda params=None: bundle, raising=False)
monkeypatch.setattr(model_runner_mod, "get_active_bundle", lambda params=None: bundle, raising=False)
monkeypatch.setattr(tinygrad_runner_mod, "CUSTOM_MODEL_PATH", str(LOCAL_MODEL_DIR), raising=False)
monkeypatch.setattr(model_runner_mod, "CUSTOM_MODEL_PATH", str(LOCAL_MODEL_DIR), raising=False)
vision_runner = TinygradRunner(ModelType.vision)
_seed_runner_inputs(vision_runner)
vision_outputs = vision_runner.run_model()
assert "pose" in vision_outputs
assert "lane_lines" in vision_outputs
policy_runner = TinygradRunner(ModelType.policy)
_seed_runner_inputs(policy_runner)
policy_outputs = policy_runner.run_model()
assert "plan" in policy_outputs
assert "desire_state" in policy_outputs

View File

@@ -0,0 +1,18 @@
from openpilot.iqpilot.selfdrive.iqmodeld import metadata, messaging, parser
from openpilot.iqpilot.selfdrive.iqmodeld.daemon import CaptureStamp, NeuralEngineState
def test_public_module_surface():
assert hasattr(messaging, "DrivePacketMemory")
assert hasattr(messaging, "pick_curvature")
assert hasattr(messaging, "populate_drive_messages")
assert hasattr(messaging, "populate_odometry_message")
assert hasattr(parser, "ArchiveParser")
assert hasattr(parser, "PhaseParser")
assert hasattr(metadata, "select_meta_layout")
assert hasattr(metadata, "build_metadata_record")
assert CaptureStamp.__name__ == "CaptureStamp"
assert NeuralEngineState.__name__ == "NeuralEngineState"

View File

@@ -0,0 +1,162 @@
from __future__ import annotations
import os
from dataclasses import dataclass
from pathlib import Path
import numpy as np
import pytest
from tinygrad.nn.onnx import OnnxRunner
from tinygrad.tensor import Tensor
import openpilot.iqpilot.selfdrive.iqmodeld.models.helpers as bundle_helpers
import openpilot.iqpilot.selfdrive.iqmodeld.models.runners.model_runner as model_runner_mod
import openpilot.iqpilot.selfdrive.iqmodeld.models.runners.tinygrad.tinygrad_runner as tinygrad_runner_mod
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.model_runner import ModelType
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.tinygrad.tinygrad_runner import TinygradRunner
SHARE_ROOT = Path(os.getenv("IQPILOT_SELECTOR_SHARE", "/Volumes/New New Vault/IQModels/models/recompiled16"))
@dataclass
class _TypeWrap:
raw: int
@dataclass
class _Artifact:
fileName: str
class _Model:
def __init__(self, model_type: int, artifact_name: str, metadata_name: str):
self.type = _TypeWrap(model_type)
self.artifact = _Artifact(artifact_name)
self.metadata = _Artifact(metadata_name)
class _Bundle:
def __init__(self, models: list[_Model], is_20hz: bool = False):
self.models = models
self.is20hz = is_20hz
def _find_selector_dirs(limit: int = 3, require_onnx: bool = False) -> list[Path]:
found: list[Path] = []
if not SHARE_ROOT.is_dir():
return found
for bundle_dir in sorted(SHARE_ROOT.iterdir()):
if not bundle_dir.is_dir():
continue
vision = next(bundle_dir.glob("driving_vision*_tinygrad.pkl"), None)
policy = next(bundle_dir.glob("driving_policy*_tinygrad.pkl"), None)
vision_meta = next(bundle_dir.glob("driving_vision*_metadata.pkl"), None)
policy_meta = next(bundle_dir.glob("driving_policy*_metadata.pkl"), None)
has_onnx = (bundle_dir / "driving_vision.onnx").is_file() and (bundle_dir / "driving_policy.onnx").is_file()
if vision and policy and vision_meta and policy_meta and (has_onnx or not require_onnx):
found.append(bundle_dir)
if len(found) >= limit:
break
return found
def _seed_runner_inputs(runner: TinygradRunner) -> None:
for name, shape in runner.input_shapes.items():
runner.inputs[name] = Tensor(
np.zeros(shape, dtype=np.float32),
device=runner.input_to_device[name],
dtype=runner.input_to_dtype[name],
).realize()
def _bundle_for_dir(bundle_dir: Path) -> _Bundle:
vision = next(bundle_dir.glob("driving_vision*_tinygrad.pkl"))
policy = next(bundle_dir.glob("driving_policy*_tinygrad.pkl"))
vision_meta = next(bundle_dir.glob("driving_vision*_metadata.pkl"))
policy_meta = next(bundle_dir.glob("driving_policy*_metadata.pkl"))
return _Bundle([
_Model(ModelType.vision, vision.name, vision_meta.name),
_Model(ModelType.policy, policy.name, policy_meta.name),
])
def _run_tinygrad_bundle(bundle_dir: Path, monkeypatch):
bundle = _bundle_for_dir(bundle_dir)
monkeypatch.setattr(bundle_helpers, "get_active_bundle", lambda params=None: bundle, raising=False)
monkeypatch.setattr(model_runner_mod, "get_active_bundle", lambda params=None: bundle, raising=False)
monkeypatch.setattr(tinygrad_runner_mod, "CUSTOM_MODEL_PATH", str(bundle_dir), raising=False)
monkeypatch.setattr(model_runner_mod, "CUSTOM_MODEL_PATH", str(bundle_dir), raising=False)
vision_runner = TinygradRunner(ModelType.vision)
_seed_runner_inputs(vision_runner)
vision_outputs = vision_runner.run_model()
policy_runner = TinygradRunner(ModelType.policy)
_seed_runner_inputs(policy_runner)
policy_outputs = policy_runner.run_model()
return vision_outputs, policy_outputs
def _run_onnx_bundle(bundle_dir: Path):
vision_session = OnnxRunner(bundle_dir / "driving_vision.onnx")
policy_session = OnnxRunner(bundle_dir / "driving_policy.onnx")
def seed_inputs(session):
seeded = {}
for name, spec in session.graph_inputs.items():
dtype_text = str(spec.dtype).lower()
if "uchar" in dtype_text or "uint8" in dtype_text:
seeded[name] = Tensor(np.zeros(spec.shape, dtype=np.uint8))
elif "half" in dtype_text or "float16" in dtype_text:
seeded[name] = Tensor(np.zeros(spec.shape, dtype=np.float16))
else:
seeded[name] = Tensor(np.zeros(spec.shape, dtype=np.float32))
return seeded
return (
vision_session(seed_inputs(vision_session))["outputs"].numpy().flatten(),
policy_session(seed_inputs(policy_session))["outputs"].numpy().flatten(),
)
@pytest.mark.skipif(not SHARE_ROOT.is_dir(), reason="selector model share is not mounted")
def test_three_selector_models_parse_via_share_onnx():
selector_dirs = _find_selector_dirs(limit=3, require_onnx=True)
assert len(selector_dirs) >= 3
for bundle_dir in selector_dirs:
vision_raw, policy_raw = _run_onnx_bundle(bundle_dir)
assert vision_raw.size > 0
assert policy_raw.size > 0
@pytest.mark.skipif(not SHARE_ROOT.is_dir(), reason="selector model share is not mounted")
def test_selector_tinygrad_pkls_execute_when_host_compatible(monkeypatch):
selector_dirs = _find_selector_dirs(limit=10)
attempted = 0
executed = 0
for bundle_dir in selector_dirs:
attempted += 1
try:
vision_outputs, policy_outputs = _run_tinygrad_bundle(bundle_dir, monkeypatch)
except AssertionError as exc:
if "Model was built on C3 or C3X" in str(exc):
continue
raise
except FileNotFoundError as exc:
if "/dev/kgsl-3d0" in str(exc):
continue
raise
assert "pose" in vision_outputs
assert "plan" in policy_outputs
executed += 1
if executed >= 3:
break
if executed == 0:
pytest.skip(f"share tinygrad pkls are QCOM-only on this host; inspected {attempted} bundles")

View File

@@ -0,0 +1,226 @@
from __future__ import annotations
import hashlib
from pathlib import Path
import pytest
from cereal import custom
from openpilot.iqpilot.selfdrive.iqmodeld.models import helpers as model_helpers
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.tinygrad import supercombo_runner as supercombo_runner_mod
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.tinygrad.supercombo_runner import (
TinygradSupercomboRunner,
)
class _Captured:
def __init__(self, expected_names):
self.expected_names = expected_names
class _FakeJit:
def __init__(self, expected_names):
self.captured = _Captured(expected_names)
class _Boom:
def __init__(self, err: Exception):
self.err = err
def __call__(self, *args, **kwargs):
raise self.err
class _FakeParams:
def __init__(self, active_bundle=None):
self.store = {}
if active_bundle is not None:
self.store["ModelManager_ActiveBundle"] = active_bundle
def get(self, key):
return self.store.get(key)
def put(self, key, value):
self.store[key] = value
def remove(self, key):
self.store.pop(key, None)
def test_verify_artifact_file_deletes_stale_cached_pkl(tmp_path: Path):
pkl_path = tmp_path / "driving_supercombo_guard.pkl"
pkl_path.write_bytes(b"stale-pkl")
runner = TinygradSupercomboRunner.__new__(TinygradSupercomboRunner)
runner._pkl_path = str(pkl_path)
runner._expected_sha256 = hashlib.sha256(b"fresh-pkl").hexdigest()
with pytest.raises(RuntimeError, match="SHA mismatch"):
runner._verify_artifact_file()
assert not pkl_path.exists()
def test_validate_jit_names_accepts_current_runtime_contract():
runner = TinygradSupercomboRunner.__new__(TinygradSupercomboRunner)
runner._pkl_path = "/tmp/does-not-matter.pkl"
runner._expected_sha256 = ""
runner._run_policy = _FakeJit(['warped', 'img_q', 'big_img_q', 'feat_q', 'desire_q', 'packed_npy_inputs'])
runner._warp_jits = {
(1344, 760): _FakeJit(['tfm', 'big_tfm', 'frame', 'big_frame']),
}
runner._validate_jit_names()
def test_validate_jit_names_raises_clear_error_for_contract_mismatch():
runner = TinygradSupercomboRunner.__new__(TinygradSupercomboRunner)
runner._pkl_path = "/tmp/does-not-matter.pkl"
runner._expected_sha256 = ""
runner._run_policy = _FakeJit(['img', 'big_img', 'feat_q', 'desire_q', 'desire', 'traffic_convention', 'action_t'])
runner._warp_jits = {
(1344, 760): _FakeJit(['img_q', 'big_img_q', 'tfm', 'big_tfm', 'frame', 'big_frame']),
}
with pytest.raises(RuntimeError, match="JIT argument mismatch"):
runner._validate_jit_names()
def test_handle_runtime_jit_mismatch_deletes_stale_cached_pkl(tmp_path: Path):
pkl_path = tmp_path / "driving_supercombo_guard.pkl"
pkl_path.write_bytes(b"stale-pkl")
runner = TinygradSupercomboRunner.__new__(TinygradSupercomboRunner)
runner._pkl_path = str(pkl_path)
runner._expected_sha256 = hashlib.sha256(b"fresh-pkl").hexdigest()
with pytest.raises(RuntimeError, match="runtime JIT mismatch with stale cached SHA"):
runner._handle_runtime_jit_mismatch(RuntimeError("args mismatch in JIT: stale bundle"))
assert not pkl_path.exists()
def test_handle_runtime_jit_mismatch_raises_clear_error_without_sha_mismatch(tmp_path: Path):
pkl_path = tmp_path / "driving_supercombo_guard.pkl"
pkl_path.write_bytes(b"fresh-pkl")
runner = TinygradSupercomboRunner.__new__(TinygradSupercomboRunner)
runner._pkl_path = str(pkl_path)
runner._expected_sha256 = hashlib.sha256(b"fresh-pkl").hexdigest()
with pytest.raises(RuntimeError, match="runtime JIT mismatch"):
runner._handle_runtime_jit_mismatch(RuntimeError("args mismatch in JIT: wrong contract"))
def test_schedule_active_bundle_redownload_sets_download_index(monkeypatch: pytest.MonkeyPatch):
params = _FakeParams({"index": 81})
monkeypatch.setattr(supercombo_runner_mod, "Params", lambda: params)
runner = TinygradSupercomboRunner.__new__(TinygradSupercomboRunner)
msg = runner._schedule_active_bundle_redownload()
assert params.get("ModelManager_DownloadIndex") == "81"
assert msg == "; scheduled automatic re-download of the active model"
def test_no_active_bundle_seeds_default_tinygrad(monkeypatch: pytest.MonkeyPatch):
monkeypatch.setattr(model_helpers, "ensure_default_model_files", lambda *a, **k: None)
params = _FakeParams()
runner = model_helpers.get_active_model_runner(params)
assert runner == custom.IQModelManager.Runner.tinygrad
active = params.get("ModelManager_ActiveBundle")
assert active is not None and active.get("ref") == "default"
def test_select_default_model_clears_custom_download_state(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
pending_restore = tmp_path / "pending_model_restore"
pending_restore.write_text("Pop")
monkeypatch.setattr(model_helpers, "_PENDING_MODEL_RESTORE_FILE", str(pending_restore))
monkeypatch.setattr(model_helpers, "ensure_default_model_files", lambda *a, **k: None)
params = _FakeParams({"index": 81, "ref": "pop"})
params.put("ModelManager_DownloadIndex", "81")
params.put("ModelRunnerTypeCache", int(custom.IQModelManager.Runner.tinygrad))
model_helpers.select_default_model(params)
assert params.get("ModelManager_DownloadIndex") is None
active = params.get("ModelManager_ActiveBundle")
assert active is not None and active.get("ref") == "default"
assert int(params.get("ModelRunnerTypeCache")) == int(custom.IQModelManager.Runner.tinygrad)
assert not pending_restore.exists()
def test_default_model_is_not_resolved_to_manifest_pop_bundle():
pop_bundle = type("Bundle", (), {"internalName": "Pop (Default)", "displayName": "Pop (Default)"})()
assert model_helpers.get_default_model_bundle([pop_bundle]) is None
def test_verify_artifact_file_schedules_redownload_for_stale_cached_pkl(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
params = _FakeParams({"index": 81})
monkeypatch.setattr(supercombo_runner_mod, "Params", lambda: params)
pkl_path = tmp_path / "driving_supercombo_guard.pkl"
pkl_path.write_bytes(b"stale-pkl")
runner = TinygradSupercomboRunner.__new__(TinygradSupercomboRunner)
runner._pkl_path = str(pkl_path)
runner._expected_sha256 = hashlib.sha256(b"fresh-pkl").hexdigest()
with pytest.raises(RuntimeError, match="scheduled automatic re-download"):
runner._verify_artifact_file()
assert params.get("ModelManager_DownloadIndex") == "81"
assert not pkl_path.exists()
def test_run_fused_converts_raw_warp_jit_mismatch_to_runtime_error(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
pkl_path = tmp_path / "driving_supercombo_guard.pkl"
pkl_path.write_bytes(b"fresh-pkl")
runner = TinygradSupercomboRunner.__new__(TinygradSupercomboRunner)
runner._pkl_path = str(pkl_path)
runner._expected_sha256 = hashlib.sha256(b"fresh-pkl").hexdigest()
runner._frame_skip = 4
runner._cam = (1344, 760)
runner._queues = {
"tfm": object(),
"big_tfm": object(),
"img_q": object(),
"big_img_q": object(),
"feat_q": object(),
"desire_q": object(),
"packed_npy_inputs": object(),
}
runner._npy = {
"tfm": [0.0],
"big_tfm": [0.0],
"desire": [0.0],
"prev_feat": [0.0],
}
runner._prev_desire = [0.0]
runner._warp_jits = {
(1344, 760): _Boom(RuntimeError("args mismatch in JIT: self.captured.expected_names=['big_frame'] != ['frame']")),
}
runner._run_policy = _FakeJit(["warped", "img_q", "big_img_q", "feat_q", "desire_q", "packed_npy_inputs"])
runner._hidden_slice = slice(0, 1)
runner._slices = {"out": slice(0, 1)}
runner._parser = type("P", (), {"parse_vision_outputs": staticmethod(lambda sliced: sliced)})()
runner._frame_tensor = lambda *args, **kwargs: object()
monkeypatch.setattr(TinygradSupercomboRunner, "_ensure_queues", lambda self, cam_w, cam_h: None)
class _Buf:
width = 1344
height = 760
data = memoryview(b"\x00")
with pytest.raises(RuntimeError, match="runtime JIT mismatch"):
runner.run_fused(
{"img": _Buf(), "big_img": _Buf()},
{"img": [0.0], "big_img": [0.0]},
{},
)

View File

@@ -0,0 +1,152 @@
from __future__ import annotations
from dataclasses import dataclass
from types import SimpleNamespace
import numpy as np
import pytest
import openpilot.iqpilot.selfdrive.iqmodeld.models.helpers as bundle_helpers
import openpilot.iqpilot.selfdrive.iqmodeld.models.runners.model_runner as runner_helpers
import openpilot.iqpilot.selfdrive.iqmodeld.daemon as iqmodeld_daemon
@dataclass
class StubOverride:
key: str
value: str
class StubBundle:
def __init__(self, generation: int = 10):
self.overrides = [StubOverride("lat", ".1"), StubOverride("long", ".3")]
self.generation = generation
class StubRunner:
def __init__(self, input_shapes: dict[str, tuple[int, ...]]) -> None:
self.input_shapes = input_shapes
self.constants = SimpleNamespace(
FULL_HISTORY_BUFFER_LEN=100,
FEATURE_LEN=512,
DESIRE_LEN=8,
PREV_DESIRED_CURV_LEN=1,
INPUT_HISTORY_BUFFER_LEN=25,
TEMPORAL_SKIP=4,
)
self.vision_input_names: list[str] = []
self.is_20hz = input_shapes.get(next(iter(input_shapes)), (1, 0, 0))[1] == 25
def prepare_inputs(self, imgs_cl, numpy_inputs, frames):
return None
def run_model(self):
return {
"hidden_state": np.zeros((1, self.constants.FEATURE_LEN), dtype=np.float32),
"desired_curvature": np.zeros((1, 1), dtype=np.float32),
}
def _install_runtime(monkeypatch: pytest.MonkeyPatch, shapes: dict[str, tuple[int, ...]], generation: int = 10):
bundle = StubBundle(generation=generation)
runner = StubRunner(shapes)
monkeypatch.setattr(bundle_helpers, "get_active_bundle", lambda params=None: bundle, raising=False)
monkeypatch.setattr(runner_helpers, "get_model_runner", lambda: runner, raising=False)
monkeypatch.setattr(iqmodeld_daemon, "get_active_bundle", lambda params=None: bundle, raising=False)
monkeypatch.setattr(iqmodeld_daemon, "get_model_runner", lambda: runner, raising=False)
return iqmodeld_daemon.NeuralEngineState(None), runner
def _expected_selector_indices(shape: tuple[int, ...], mode: str) -> np.ndarray | None:
if mode == "split":
full = 100
return np.arange(full)[-1 - (4 * (25 - 1))::4]
if mode == "20hz":
step = int(-100 / shape[1])
return np.arange(step, step * (shape[1] + 1), step)[::-1]
if mode == "dense":
return np.arange(shape[1])
return None
@pytest.mark.parametrize(
("shapes", "mode"),
[
({"desire": (1, 100, 8), "features_buffer": (1, 99, 512), "prev_desired_curv": (1, 100, 1)}, "dense"),
({"desire": (1, 25, 8), "features_buffer": (1, 24, 512)}, "20hz"),
({"desire_pulse": (1, 25, 8), "features_buffer": (1, 25, 512)}, "split"),
],
)
def test_replay_ledger_layout_matches_expected_history(monkeypatch: pytest.MonkeyPatch,
shapes: dict[str, tuple[int, ...]],
mode: str):
state, _runner = _install_runtime(monkeypatch, shapes)
for tensor_name, tensor_shape in shapes.items():
history = state.temporal_buffers.get(tensor_name)
selector = state.temporal_idxs_map.get(tensor_name)
if history is None:
continue
if mode == "dense":
expected_shape = (1, tensor_shape[1], tensor_shape[2])
else:
expected_shape = (1, 100, tensor_shape[2])
assert history.shape == expected_shape
expected_selector = _expected_selector_indices(tensor_shape, mode)
if expected_selector is None:
assert selector is None or selector.size == 0
else:
assert np.array_equal(selector, expected_selector)
def test_replay_ledger_rising_edge_and_hidden_state_updates(monkeypatch: pytest.MonkeyPatch):
state, runner = _install_runtime(monkeypatch, {
"desire": (1, 100, 8),
"features_buffer": (1, 99, 512),
"prev_desired_curv": (1, 100, 1),
})
pulse = np.zeros(8, dtype=np.float32)
pulse[3] = 1.0
state.run({}, {}, {"desire": pulse})
first_export = state.numpy_inputs["desire"].copy()
assert np.count_nonzero(first_export) == 1
state.run({}, {}, {"desire": pulse})
second_export = state.numpy_inputs["desire"].copy()
assert np.count_nonzero(second_export) == 1
assert second_export[0, -1, 3] == 0.0
hidden_value = np.arange(runner.constants.FEATURE_LEN, dtype=np.float32)
def hidden_state_run():
return {
"hidden_state": hidden_value.reshape(1, -1),
"desired_curvature": np.array([[0.25]], dtype=np.float32),
}
state.model_runner.run_model = hidden_state_run
state.run({}, {}, {"desire": np.zeros(8, dtype=np.float32)})
np.testing.assert_allclose(state.numpy_inputs["features_buffer"][0, -1], hidden_value, rtol=0, atol=0)
assert state.numpy_inputs["prev_desired_curv"][0, -1, 0] == pytest.approx(0.25)
def test_replay_ledger_zeroes_feedback_for_mlsim_generation(monkeypatch: pytest.MonkeyPatch):
state, _runner = _install_runtime(monkeypatch, {
"desire": (1, 100, 8),
"features_buffer": (1, 99, 512),
"prev_desired_curv": (1, 100, 1),
}, generation=11)
def ml_run():
return {
"hidden_state": np.zeros((1, 512), dtype=np.float32),
"desired_curvature": np.array([[1.5]], dtype=np.float32),
}
state.model_runner.run_model = ml_run
state.run({}, {}, {"desire": np.zeros(8, dtype=np.float32)})
assert np.count_nonzero(state.numpy_inputs["prev_desired_curv"]) == 0

View File

@@ -0,0 +1,17 @@
#!/usr/bin/env bash
# Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
set -euo pipefail
TF_ROOT="${TF_ROOT:-/home/batman/one/external/tensorflow}"
TF_INCLUDE_DIR="${TF_INCLUDE_DIR:-$TF_ROOT/include}"
TF_LIB_DIR="${TF_LIB_DIR:-$TF_ROOT/lib}"
CXX="${CXX:-clang++}"
exec "$CXX" \
-std=c++17 \
-I "$TF_INCLUDE_DIR" \
-L "$TF_LIB_DIR" \
-Wl,-rpath="$TF_LIB_DIR" \
main.cc \
-ltensorflow

View File

@@ -0,0 +1,92 @@
// Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <memory>
#include <string>
#include <vector>
#include "tensorflow/c/c_api.h"
namespace {
struct FileBlob {
std::vector<uint8_t> bytes;
};
FileBlob read_blob(const std::filesystem::path &path) {
FILE *handle = fopen(path.c_str(), "rb");
if (handle == nullptr) {
return {};
}
fseek(handle, 0, SEEK_END);
const long byte_count = ftell(handle);
rewind(handle);
FileBlob blob;
blob.bytes.resize(byte_count);
const size_t read_count = fread(blob.bytes.data(), static_cast<size_t>(byte_count), 1, handle);
fclose(handle);
if (read_count != 1) {
blob.bytes.clear();
}
return blob;
}
void free_tf_buffer(void *data, size_t) {
free(data);
}
TF_Buffer *make_tf_buffer(FileBlob &&blob) {
auto *buffer = TF_NewBuffer();
auto *payload = static_cast<uint8_t *>(malloc(blob.bytes.size()));
assert(payload != nullptr);
memcpy(payload, blob.bytes.data(), blob.bytes.size());
buffer->data = payload;
buffer->length = blob.bytes.size();
buffer->data_deallocator = free_tf_buffer;
return buffer;
}
std::string pb_path_from_prefix(const char *prefix) {
return std::string(prefix) + ".pb";
}
} // namespace
int main(int argc, char *argv[]) {
if (argc < 2) {
printf("usage: %s <graph-prefix>\n", argv[0]);
return 1;
}
const std::string pb_path = pb_path_from_prefix(argv[1]);
printf("loading model %s\n", pb_path.c_str());
FileBlob blob = read_blob(pb_path);
if (blob.bytes.empty()) {
printf("FAIL: unable to read graph bytes\n");
return 1;
}
printf("loaded model of size %zu\n", blob.bytes.size());
std::unique_ptr<TF_Status, decltype(&TF_DeleteStatus)> status(TF_NewStatus(), TF_DeleteStatus);
std::unique_ptr<TF_Graph, decltype(&TF_DeleteGraph)> graph(TF_NewGraph(), TF_DeleteGraph);
std::unique_ptr<TF_ImportGraphDefOptions, decltype(&TF_DeleteImportGraphDefOptions)> options(
TF_NewImportGraphDefOptions(), TF_DeleteImportGraphDefOptions);
std::unique_ptr<TF_Buffer, decltype(&TF_DeleteBuffer)> buffer(make_tf_buffer(std::move(blob)), TF_DeleteBuffer);
TF_GraphImportGraphDef(graph.get(), buffer.get(), options.get(), status.get());
if (TF_GetCode(status.get()) != TF_OK) {
printf("FAIL: %s\n", TF_Message(status.get()));
return 1;
}
printf("SUCCESS\n");
return 0;
}

View File

@@ -0,0 +1,32 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
from __future__ import annotations
import sys
from pathlib import Path
import tensorflow as tf
def _load_graph_bytes(graph_path: Path) -> bytes:
return graph_path.read_bytes()
def _parse_graph(graph_path: Path) -> tf.compat.v1.GraphDef:
graph = tf.compat.v1.GraphDef()
graph.ParseFromString(_load_graph_bytes(graph_path))
return graph
def main(argv: list[str]) -> int:
if len(argv) < 2:
print("Usage: pb_loader.py <graph.pb>")
return 1
_parse_graph(Path(argv[1]))
return 0
if __name__ == "__main__":
raise SystemExit(main(sys.argv))

View File

@@ -0,0 +1,54 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
from __future__ import annotations
import os
import time
import numpy as np
import cereal.messaging as messaging
from openpilot.system.manager.process_config import managed_processes
RUN_COUNT = int(os.getenv("N", "5"))
WINDOW_SECONDS = int(os.getenv("TIME", "30"))
WARMUP_MESSAGES = 10
def _collect_execution_samples(sock, duration_s: int) -> np.ndarray:
samples: list[float] = []
deadline = time.monotonic() + duration_s
while time.monotonic() < deadline:
for message in messaging.drain_sock(sock, wait_for_one=True):
samples.append(message.modelV2.modelExecutionTime)
return np.array(samples[WARMUP_MESSAGES:]) * 1000.0
def _single_benchmark_pass(sock) -> np.ndarray:
os.environ["LOGPRINT"] = "debug"
managed_processes["modeld"].start()
time.sleep(5)
try:
return _collect_execution_samples(sock, WINDOW_SECONDS)
finally:
managed_processes["modeld"].stop()
def _report_run(index: int, values_ms: np.ndarray) -> None:
print(
f"run {index}: avg={values_ms.mean():0.2f}ms "
f"min={values_ms.min():0.2f}ms max={values_ms.max():0.2f}ms"
)
if __name__ == "__main__":
subscriber = messaging.sub_sock("modelV2", conflate=False, timeout=1000)
all_runs = [_single_benchmark_pass(subscriber) for _ in range(RUN_COUNT)]
print("\n")
print(f"ran modeld {RUN_COUNT} times for {WINDOW_SECONDS}s each")
for index, values_ms in enumerate(all_runs, start=1):
_report_run(index, values_ms)
print("\n")

View File

@@ -0,0 +1,12 @@
#!/usr/bin/env python3
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
from __future__ import annotations
from openpilot.iqpilot.selfdrive.iqmodeld.tools.daemon_jit_compiler import main
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -0,0 +1,417 @@
#!/usr/bin/env python3
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
from __future__ import annotations
import argparse
import atexit
import os
import pickle
import time
from dataclasses import dataclass
from functools import partial
import numpy as np
def _patch_firmware_fetch() -> None:
import hashlib
import pathlib
import zstandard
from tinygrad import helpers
if not hasattr(helpers, "fetch_fw"):
return
original_fetch = helpers.fetch_fw
def fetch_fw(path, name, sha256):
archive_path = pathlib.Path(f"/lib/firmware/{path}/{name}.zst")
if archive_path.is_file():
blob = zstandard.ZstdDecompressor().stream_reader(archive_path.read_bytes()).read()
if hashlib.sha256(blob).hexdigest() == sha256:
return blob
return original_fetch(path, name, sha256)
helpers.fetch_fw = fetch_fw
_patch_firmware_fetch()
from tinygrad.device import Device
from tinygrad.engine.jit import TinyJit
from tinygrad.helpers import Context
from tinygrad.nn.onnx import OnnxRunner
from tinygrad.tensor import Tensor
@dataclass(frozen=True)
class CameraGeometry:
width: int
height: int
stride: int
y_height: int
uv_height: int
size: int
WARP_DEVICE = os.getenv("WARP_DEV")
def _read_shared_copy(path: str) -> str:
from openpilot.common.file_chunker import read_file_chunked
from openpilot.system.hardware.hw import Paths
shm_path = os.path.join(Paths.shm_path(), os.path.basename(path))
atexit.register(lambda: os.path.exists(shm_path) and os.remove(shm_path))
with open(shm_path, "wb") as handle:
handle.write(read_file_chunked(path))
return shm_path
def _parse_size(text: str) -> tuple[int, int]:
width, height = text.lower().split("x")
return int(width), int(height)
def _rand_u8_inputs(keys: list[str], shape, device=None):
return {key: Tensor.randint(shape, low=0, high=256, dtype="uint8", device=device).realize() for key in keys}
def _phase_desire_key(policy_shapes: dict[str, tuple[int, ...]]) -> str:
for key in policy_shapes:
if key.startswith("desire"):
return key
raise KeyError("No desire-like key found in policy shapes")
def _phase_image_keys(vision_shapes: dict[str, tuple[int, ...]]) -> tuple[str, str]:
names = sorted(name for name in vision_shapes if "img" in name)
road_key = next((name for name in names if "big" not in name), None)
wide_key = next((name for name in names if "big" in name), None)
if road_key is None or wide_key is None:
raise ValueError(f"Unable to resolve road/wide image keys from {list(vision_shapes)}")
return road_key, wide_key
def _base_policy_keys(policy_shapes: dict[str, tuple[int, ...]]) -> set[str]:
return {
_phase_desire_key(policy_shapes),
"features_buffer",
"traffic_convention",
"action_t",
}
def _common_policy_shapes(role_shapes: dict[str, dict[str, tuple[int, ...]]]) -> dict[str, tuple[int, ...]]:
first_role = next(iter(role_shapes))
baseline = role_shapes[first_role]
for role_name, shape_map in role_shapes.items():
if shape_map != baseline:
raise ValueError(f"Policy input shapes differ for role {role_name}")
return baseline
def _phase_frame_skip(policy_shapes: dict[str, tuple[int, ...]]) -> int:
feature_shape = policy_shapes.get("features_buffer")
if feature_shape is None:
return 1
history_length = feature_shape[1]
return 1 if history_length >= 99 else 4
def _project_pixels(src_flat, inverse_matrix, dst_shape, src_shape, stride_pad, border_fill_val=None):
dst_w, dst_h = dst_shape
src_h, src_w = src_shape
x_coords = Tensor.arange(dst_w, device=WARP_DEVICE).reshape(1, dst_w).expand(dst_h, dst_w).reshape(-1)
y_coords = Tensor.arange(dst_h, device=WARP_DEVICE).reshape(dst_h, 1).expand(dst_h, dst_w).reshape(-1)
src_x = inverse_matrix[0, 0] * x_coords + inverse_matrix[0, 1] * y_coords + inverse_matrix[0, 2]
src_y = inverse_matrix[1, 0] * x_coords + inverse_matrix[1, 1] * y_coords + inverse_matrix[1, 2]
scale = inverse_matrix[2, 0] * x_coords + inverse_matrix[2, 1] * y_coords + inverse_matrix[2, 2]
src_x = src_x / scale
src_y = src_y / scale
rounded_x = Tensor.round(src_x)
rounded_y = Tensor.round(src_y)
gather_x = rounded_x.clip(0, src_w - 1).cast("int")
gather_y = rounded_y.clip(0, src_h - 1).cast("int")
gather_index = gather_y * (src_w + stride_pad) + gather_x
sampled = src_flat[gather_index]
if border_fill_val is None:
return sampled
inside = ((rounded_x >= 0) & (rounded_x <= src_w - 1) & (rounded_y >= 0) & (rounded_y <= src_h - 1)).cast(sampled.dtype)
return sampled * inside + Tensor(border_fill_val, dtype=sampled.dtype) * (1 - inside)
def _pack_nv12_planes(stacked_frame):
y_height = (stacked_frame.shape[0] * 2) // 3
frame_width = stacked_frame.shape[1]
return Tensor.cat(
stacked_frame[0:y_height:2, 0::2],
stacked_frame[1:y_height:2, 0::2],
stacked_frame[0:y_height:2, 1::2],
stacked_frame[1:y_height:2, 1::2],
stacked_frame[y_height:y_height + y_height // 4].reshape((y_height // 2, frame_width // 2)),
stacked_frame[y_height + y_height // 4:y_height + y_height // 2].reshape((y_height // 2, frame_width // 2)),
dim=0,
).reshape((6, y_height // 2, frame_width // 2))
def _warp_program(camera: CameraGeometry, model_w: int, model_h: int):
uv_offset = camera.stride * camera.y_height
stride_pad = camera.stride - camera.width
def prepare_frame(nv12_blob, inverse_matrix):
uv_matrix = inverse_matrix * Tensor([[1.0, 1.0, 0.5], [1.0, 1.0, 0.5], [2.0, 2.0, 1.0]], device=WARP_DEVICE)
uv_plane = nv12_blob[uv_offset:uv_offset + camera.uv_height * camera.stride].reshape(camera.uv_height, camera.stride)
with Context(SPLIT_REDUCEOP=0):
y_plane = _project_pixels(nv12_blob[:camera.height * camera.stride], inverse_matrix, (model_w, model_h), (camera.height, camera.width), stride_pad).realize()
u_plane = _project_pixels(uv_plane[:camera.height // 2, :camera.width:2].flatten(), uv_matrix, (model_w // 2, model_h // 2), (camera.height // 2, camera.width // 2), 0).realize()
v_plane = _project_pixels(uv_plane[:camera.height // 2, 1:camera.width:2].flatten(), uv_matrix, (model_w // 2, model_h // 2), (camera.height // 2, camera.width // 2), 0).realize()
return _pack_nv12_planes(y_plane.cat(u_plane).cat(v_plane).reshape((model_h * 3 // 2, model_w)))
return prepare_frame
def _sample_sparse(queue_tensor, frame_stride):
return queue_tensor[::frame_stride].contiguous().flatten(0, 1).unsqueeze(0)
def _sample_desire(queue_tensor, frame_stride):
return queue_tensor.reshape(-1, frame_stride, *queue_tensor.shape[1:]).max(1).flatten(0, 1).unsqueeze(0)
def _roll_queue(queue_tensor, incoming, sampler):
queue_tensor.assign(queue_tensor[1:].cat(incoming, dim=0).contiguous())
return sampler(queue_tensor)
def _vision_queue_buffers(vision_shapes: dict[str, tuple[int, ...]], frame_stride: int, device):
road_key, _ = _phase_image_keys(vision_shapes)
image_shape = vision_shapes[road_key]
frame_history = image_shape[1] // 6
queue_depth = frame_stride * (frame_history - 1) + 1
frame_queue_shape = (queue_depth, 6, image_shape[2], image_shape[3])
numpy_state = {
"tfm": np.zeros((3, 3), dtype=np.float32),
"big_tfm": np.zeros((3, 3), dtype=np.float32),
}
tensor_state = {
"img_q": Tensor(np.zeros(frame_queue_shape, dtype=np.uint8), device=device).contiguous().realize(),
"big_img_q": Tensor(np.zeros(frame_queue_shape, dtype=np.uint8), device=device).contiguous().realize(),
**{name: Tensor(value, device="NPY").realize() for name, value in numpy_state.items()},
}
return tensor_state, numpy_state
def _policy_queue_buffers(vision_shapes: dict[str, tuple[int, ...]], policy_shapes: dict[str, tuple[int, ...]], frame_stride: int, device):
tensor_state, numpy_state = _vision_queue_buffers(vision_shapes, frame_stride, device)
desired_key = _phase_desire_key(policy_shapes)
feature_shape = policy_shapes["features_buffer"]
desired_shape = policy_shapes[desired_key]
traffic_shape = policy_shapes["traffic_convention"]
action_shape = policy_shapes.get("action_t", traffic_shape)
numpy_policy = {
"desire": np.zeros(desired_shape[2], dtype=np.float32),
"traffic_convention": np.zeros(traffic_shape, dtype=np.float32),
"action_t": np.zeros(action_shape, dtype=np.float32),
}
for key, shape in policy_shapes.items():
if key not in _base_policy_keys(policy_shapes):
numpy_policy[key] = np.zeros(shape, dtype=np.float32)
numpy_state.update(numpy_policy)
tensor_state.update({
"feat_q": Tensor(np.zeros((frame_stride * (feature_shape[1] - 1) + 1, feature_shape[0], feature_shape[2]), dtype=np.float32), device=device).contiguous().realize(),
"desire_q": Tensor(np.zeros((frame_stride * desired_shape[1], desired_shape[0], desired_shape[2]), dtype=np.float32), device=device).contiguous().realize(),
**{name: Tensor(value, device="NPY").realize() for name, value in numpy_policy.items()},
})
return tensor_state, numpy_state
def _stage_program(camera: CameraGeometry, model_w: int, model_h: int, frame_stride: int):
prepare_frame = _warp_program(camera, model_w, model_h)
sparse_sampler = partial(_sample_sparse, frame_stride=frame_stride)
def stage_inputs(img_q, big_img_q, tfm, big_tfm, frame, big_frame):
tfm = tfm.to(WARP_DEVICE)
big_tfm = big_tfm.to(WARP_DEVICE)
Tensor.realize(tfm, big_tfm)
staged_main = prepare_frame(frame, tfm).unsqueeze(0).to(Device.DEFAULT)
staged_wide = prepare_frame(big_frame, big_tfm).unsqueeze(0).to(Device.DEFAULT)
return (
_roll_queue(img_q, staged_main, sparse_sampler),
_roll_queue(big_img_q, staged_wide, sparse_sampler),
)
return stage_inputs
def _role_executor(model_runners: dict[str, OnnxRunner], meta_by_role: dict[str, dict], frame_stride: int):
desired_sampler = partial(_sample_desire, frame_stride=frame_stride)
sparse_sampler = partial(_sample_sparse, frame_stride=frame_stride)
vision_hidden_slice = meta_by_role["vision"]["output_slices"]["hidden_state"]
policy_roles = [name for name in meta_by_role if name != "vision"]
policy_shapes = _common_policy_shapes({name: meta_by_role[name]["input_shapes"] for name in policy_roles})
desired_key = _phase_desire_key(policy_shapes)
road_key, wide_key = _phase_image_keys(meta_by_role["vision"]["input_shapes"])
extra_keys = [key for key in policy_shapes if key not in _base_policy_keys(policy_shapes)]
def execute_bundle(img, big_img, feat_q, desire_q, desire, traffic_convention, action_t, **extra):
desired_tensor = desire.to(Device.DEFAULT)
traffic_tensor = traffic_convention.to(Device.DEFAULT)
action_tensor = action_t.to(Device.DEFAULT)
extra_tensors = {key: extra[key].to(Device.DEFAULT) for key in extra_keys if key in extra}
Tensor.realize(desired_tensor, traffic_tensor, action_tensor, *extra_tensors.values())
desire_buffer = _roll_queue(desire_q, desired_tensor.reshape(1, 1, -1), desired_sampler)
vision_output = next(iter(model_runners["vision"]({road_key: img, wide_key: big_img}).values())).cast("float32")
hidden_state = vision_output[:, vision_hidden_slice].reshape(1, -1).unsqueeze(0)
feature_buffer = _roll_queue(feat_q, hidden_state, sparse_sampler)
common_inputs = {
"features_buffer": feature_buffer,
desired_key: desire_buffer,
"traffic_convention": traffic_tensor,
"action_t": action_tensor,
**extra_tensors,
}
role_outputs = []
for role_name in policy_roles:
role_outputs.append(next(iter(model_runners[role_name](common_inputs).values())).cast("float32"))
return (vision_output, *role_outputs)
return execute_bundle
def _capture_and_freeze(jit_runner, random_inputs_factory, queue_keys, queue_factory):
seed_value = 42
def validate(fn, baseline_outputs=None, baseline_buffers=None, expect_match=True, replay_seed=seed_value):
queue_tensors, numpy_values = queue_factory(Device.DEFAULT)
np.random.seed(replay_seed)
Tensor.manual_seed(replay_seed)
replay_count = 1 if (baseline_outputs is not None or baseline_buffers is not None) else 3
for pass_index in range(replay_count):
for value in numpy_values.values():
value[:] = np.random.randn(*value.shape).astype(value.dtype)
Device.default.synchronize()
random_inputs = random_inputs_factory()
start_time = time.perf_counter()
outputs = fn(**{name: queue_tensors[name] for name in queue_keys}, **random_inputs)
enqueue_time = time.perf_counter()
Device.default.synchronize()
total_time = time.perf_counter()
print(f" [{pass_index + 1}/{replay_count}] enqueue {(enqueue_time - start_time) * 1e3:6.2f} ms -- total {(total_time - start_time) * 1e3:6.2f} ms")
if pass_index == 0:
output_snapshot = [np.copy(value.numpy()) for value in outputs]
buffer_snapshot = [np.copy(value.numpy().copy()) for value in queue_tensors.values()]
if baseline_outputs is not None:
matches = all(np.array_equal(current, reference) for current, reference in zip(output_snapshot, baseline_outputs, strict=True))
assert matches == expect_match, f"outputs {'differ from' if expect_match else 'match'} baseline"
if baseline_buffers is not None:
matches = all(np.array_equal(current, reference) for current, reference in zip(buffer_snapshot, baseline_buffers, strict=True))
assert matches == expect_match, f"buffers {'differ from' if expect_match else 'match'} baseline"
return output_snapshot, buffer_snapshot
print("capture + replay")
baseline_outputs, baseline_buffers = validate(jit_runner)
print("pickle round trip")
frozen = pickle.loads(pickle.dumps(jit_runner))
validate(frozen, baseline_outputs, baseline_buffers, expect_match=True)
validate(frozen, baseline_outputs, baseline_buffers, expect_match=False, replay_seed=seed_value + 1)
return frozen
def _arg_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser()
parser.add_argument("--model-size", type=_parse_size, required=True, help="model input WxH")
parser.add_argument("--camera-resolutions", type=_parse_size, nargs="+", required=True, help="camera resolutions WxH")
parser.add_argument("--vision-onnx", required=True)
parser.add_argument("--policy-onnx")
parser.add_argument("--off-policy-onnx")
parser.add_argument("--on-policy-onnx")
parser.add_argument("--output", required=True)
parser.add_argument("--frame-skip", type=int)
return parser
def main(argv: list[str] | None = None) -> int:
from openpilot.iqpilot.selfdrive.iqmodeld.metadata import build_metadata_record
from openpilot.system.camerad.cameras.nv12_info import get_nv12_info
args = _arg_parser().parse_args(argv)
model_w, model_h = args.model_size
policy_specs = [
("policy", args.policy_onnx),
("off_policy", args.off_policy_onnx),
("on_policy", args.on_policy_onnx),
]
active_policy_specs = [(role, path) for role, path in policy_specs if path]
if not active_policy_specs:
raise SystemExit("At least one policy ONNX must be provided")
model_paths = {"vision": _read_shared_copy(args.vision_onnx)}
for role_name, onnx_path in active_policy_specs:
model_paths[role_name] = _read_shared_copy(onnx_path)
model_runners = {role_name: OnnxRunner(path) for role_name, path in model_paths.items()}
meta_by_role = {role_name: build_metadata_record(path) for role_name, path in model_paths.items()}
shared_policy_shapes = _common_policy_shapes({
role_name: meta_by_role[role_name]["input_shapes"] for role_name, _ in active_policy_specs
})
frame_stride = args.frame_skip if args.frame_skip is not None else _phase_frame_skip(shared_policy_shapes)
package: dict[Any, Any] = {
"meta_by_role": meta_by_role,
"roles": [role_name for role_name, _ in active_policy_specs],
"frame_stride": frame_stride,
}
executor_jit = TinyJit(_role_executor(model_runners, meta_by_role, frame_stride), prune=True)
queue_factory = partial(_policy_queue_buffers, meta_by_role["vision"]["input_shapes"], shared_policy_shapes, frame_stride)
image_shape = meta_by_role["vision"]["input_shapes"][_phase_image_keys(meta_by_role["vision"]["input_shapes"])[0]]
package["execute_bundle"] = _capture_and_freeze(
executor_jit,
partial(_rand_u8_inputs, keys=["img", "big_img"], shape=image_shape),
["feat_q", "desire_q", "desire", "traffic_convention", "action_t", *[k for k in shared_policy_shapes if k not in _base_policy_keys(shared_policy_shapes)]],
queue_factory,
)
for camera_width, camera_height in args.camera_resolutions:
camera = CameraGeometry(camera_width, camera_height, *get_nv12_info(camera_width, camera_height))
stage_jit = TinyJit(_stage_program(camera, model_w, model_h, frame_stride), prune=True)
package[(camera_width, camera_height)] = {
"stage_inputs": _capture_and_freeze(
stage_jit,
partial(_rand_u8_inputs, keys=["frame", "big_frame"], shape=camera.size, device=WARP_DEVICE),
["img_q", "big_img_q", "tfm", "big_tfm"],
partial(_vision_queue_buffers, meta_by_role["vision"]["input_shapes"], frame_stride),
)
}
with open(args.output, "wb") as handle:
pickle.dump(package, handle)
print(f"Saved combined split runtime to {args.output} ({os.path.getsize(args.output) / 1e6:.2f} MB)")
return 0
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -0,0 +1,427 @@
#!/usr/bin/env python3
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
import argparse
import atexit
import math
import os
import pickle
import re
import tempfile
import time
from functools import partial
from collections import namedtuple
import numpy as np
def _patch_tinygrad_fetch_fw():
import hashlib
import pathlib
import zstandard
from tinygrad import helpers
_orig = helpers.fetch_fw
def fetch_fw(path, name, sha256):
p = pathlib.Path(f"/lib/firmware/{path}/{name}.zst")
if p.is_file():
blob = zstandard.ZstdDecompressor().stream_reader(p.read_bytes()).read()
if hashlib.sha256(blob).hexdigest() == sha256:
return blob
return _orig(path, name, sha256)
helpers.fetch_fw = fetch_fw
_patch_tinygrad_fetch_fw()
from tinygrad.tensor import Tensor
from tinygrad.helpers import Context
from tinygrad.device import Device
from tinygrad.engine.jit import TinyJit
NV12Frame = namedtuple("NV12Frame", ['width', 'height', 'stride', 'y_height', 'uv_height', 'size'])
WARP_INPUTS = ['tfm', 'big_tfm']
POLICY_INPUTS = ['img_q', 'big_img_q', 'feat_q', 'desire_q', 'packed_npy_inputs']
UV_SCALE_MATRIX = np.array([[0.5, 0, 0], [0, 0.5, 0], [0, 0, 1]], dtype=np.float32)
UV_SCALE_MATRIX_INV = np.linalg.inv(UV_SCALE_MATRIX)
WARP_DEV = os.getenv('WARP_DEV')
def make_random_images(keys, shape, device=None):
return {k: Tensor.randint(shape, low=0, high=256, dtype='uint8', device=device).realize() for k in keys}
def warp_perspective_tinygrad(src_flat, M_inv, dst_shape, src_shape, stride_pad, border_fill_val=None):
w_dst, h_dst = dst_shape
h_src, w_src = src_shape
x = Tensor.arange(w_dst).reshape(1, w_dst).expand(h_dst, w_dst).reshape(-1)
y = Tensor.arange(h_dst).reshape(h_dst, 1).expand(h_dst, w_dst).reshape(-1)
# inline 3x3 matmul as elementwise to avoid reduce op (enables fusion with gather)
src_x = M_inv[0, 0] * x + M_inv[0, 1] * y + M_inv[0, 2]
src_y = M_inv[1, 0] * x + M_inv[1, 1] * y + M_inv[1, 2]
src_w = M_inv[2, 0] * x + M_inv[2, 1] * y + M_inv[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, w_src - 1).cast('int')
y_nn_clipped = y_round.clip(0, h_src - 1).cast('int')
idx = y_nn_clipped * (w_src + stride_pad) + x_nn_clipped
sampled = src_flat[idx]
if border_fill_val is None:
return sampled
in_bounds = ((x_round >= 0) & (x_round <= w_src - 1) &
(y_round >= 0) & (y_round <= h_src - 1)).cast(sampled.dtype)
return sampled * in_bounds + Tensor(border_fill_val, dtype=sampled.dtype) * (1 - in_bounds)
def frames_to_tensor(frames):
H = (frames.shape[0] * 2) // 3
W = frames.shape[1]
in_img1 = Tensor.cat(frames[0:H:2, 0::2],
frames[1:H:2, 0::2],
frames[0:H:2, 1::2],
frames[1:H:2, 1::2],
frames[H:H+H//4].reshape((H//2, W//2)),
frames[H+H//4:H+H//2].reshape((H//2, W//2)), dim=0).reshape((6, H//2, W//2))
return in_img1
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_tinygrad(input_frame, M_inv):
# UV_SCALE @ M_inv @ UV_SCALE_INV simplifies to elementwise scaling
M_inv_uv = M_inv * Tensor([[1.0, 1.0, 0.5], [1.0, 1.0, 0.5], [2.0, 2.0, 1.0]], device=WARP_DEV)
# deinterleave NV12 UV plane (UVUV... -> separate U, V)
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],
M_inv, (model_w, model_h),
(cam_h, cam_w), stride_pad).realize()
u = warp_perspective_tinygrad(uv[:cam_h//2, :cam_w:2].flatten(),
M_inv_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(),
M_inv_uv, (model_w//2, model_h//2),
(cam_h//2, cam_w//2), 0).realize()
yuv = y.cat(u).cat(v).reshape((model_h * 3 // 2, model_w))
tensor = frames_to_tensor(yuv)
return tensor
return frame_prepare_tinygrad
def make_warp_input_queues(vision_input_shapes, frame_skip, device):
img = vision_input_shapes['img'] # (1, 12, 128, 256)
n_frames = img[1] // 6
img_buf_shape = (frame_skip * (n_frames - 1) + 1, 6, img[2], img[3])
npy = {
'tfm': np.zeros((3, 3), dtype=np.float32),
'big_tfm': np.zeros((3, 3), dtype=np.float32),
}
input_queues = {
'img_q': Tensor(np.zeros(img_buf_shape, dtype=np.uint8), device=device).contiguous().realize(),
'big_img_q': Tensor(np.zeros(img_buf_shape, dtype=np.uint8), device=device).contiguous().realize(),
**{k: Tensor(v, device='NPY').realize() for k, v in npy.items()},
}
return input_queues, npy
def get_policy_npy_shapes(input_shapes):
dp = input_shapes['desire_pulse'] # (1, 25, 8)
tc = input_shapes['traffic_convention'] # (1, 2)
at = input_shapes['action_t'] # (1, 2)
fb = input_shapes['features_buffer'] # (1, 24, 512)
# TODO prev_feat shouldn't exist and be handled inside the JIT, but corrupt on QCOM for now
shapes = {'desire': (dp[2],), 'traffic_convention': tuple(tc), 'action_t': tuple(at), 'prev_feat': (fb[0], fb[2])}
return shapes, [math.prod(s) for s in shapes.values()]
def make_input_queues(input_shapes, frame_skip, device):
input_queues, npy = make_warp_input_queues(input_shapes, frame_skip, device)
fb = input_shapes['features_buffer'] # (1, 24, 512), past features only; the model appends the current frame's feature
dp = input_shapes['desire_pulse'] # (1, 25, 8)
shapes, sizes = get_policy_npy_shapes(input_shapes)
packed_npy_inputs = np.zeros(sum(sizes), dtype=np.float32)
# views into the packed inputs, to be refilled at runtime
npy.update({k: v.reshape(s) for (k, s), v in zip(shapes.items(), np.split(packed_npy_inputs, np.cumsum(sizes[:-1])), strict=True)})
input_queues.update({
'feat_q': Tensor(np.zeros((frame_skip * fb[1], fb[0], fb[2]), dtype=np.float32), device=device).contiguous().realize(),
'desire_q': Tensor(np.zeros((frame_skip * dp[1], dp[0], dp[2]), dtype=np.float32), device=device).contiguous().realize(),
'packed_npy_inputs': Tensor(packed_npy_inputs, device='NPY').realize(),
})
return input_queues, npy
def shift_and_sample(buf, new_val, sample_fn):
buf.assign(buf[1:].cat(new_val, dim=0).contiguous())
return sample_fn(buf)
def sample_skip(buf, frame_skip):
return buf[::frame_skip].contiguous().flatten(0, 1).unsqueeze(0)
def sample_desire(buf, frame_skip):
return buf.reshape(-1, frame_skip, *buf.shape[1:]).max(1).flatten(0, 1).unsqueeze(0)
def make_warp(nv12, model_w, model_h, frame_skip):
frame_prepare = make_frame_prepare(nv12, model_w, model_h)
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)
warped_frame = frame_prepare(frame, tfm).unsqueeze(0)
warped_big_frame = frame_prepare(big_frame, big_tfm).unsqueeze(0)
return Tensor.cat(warped_frame, warped_big_frame)
return warp
def make_run_policy(model_runner, model_metadata, frame_skip):
sample_desire_fn = partial(sample_desire, frame_skip=frame_skip)
sample_skip_fn = partial(sample_skip, frame_skip=frame_skip)
npy_shapes, npy_sizes = get_policy_npy_shapes(model_metadata['input_shapes'])
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)
desire, traffic_convention, action_t, prev_feat = (t.reshape(s) for t, s in zip(packed_npy_inputs.split(npy_sizes), npy_shapes.values(), strict=True))
desire_buf = shift_and_sample(desire_q, desire.reshape(1, 1, -1), sample_desire_fn)
feat_buf = shift_and_sample(feat_q, prev_feat.reshape(1, 1, -1), sample_skip_fn)
inputs = {
'img': img,
'big_img': big_img,
'features_buffer': feat_buf,
'desire_pulse': desire_buf,
'traffic_convention': traffic_convention,
'action_t': action_t,
}
out = next(iter(model_runner(inputs).values())).cast('float32')
return out,
return run_policy
def compile_jit(jit, make_random_inputs, input_keys, make_queues):
SEED = 42
def random_inputs_run(fn, seed, test_val=None, test_buffers=None, expect_match=True):
input_queues, npy = make_queues(Device.DEFAULT)
np.random.seed(seed)
Tensor.manual_seed(seed)
testing = test_val is not None or test_buffers is not None
n_runs = 1 if testing else 3
for i in range(n_runs):
for v in npy.values():
v[:] = np.random.randn(*v.shape).astype(v.dtype)
Device.default.synchronize()
random_inputs = make_random_inputs()
st = time.perf_counter()
outs = fn(**{k: input_queues[k] for k in input_keys}, **random_inputs)
mt = time.perf_counter()
Device.default.synchronize()
et = time.perf_counter()
print(f" [{i+1}/{n_runs}] enqueue {(mt-st)*1e3:6.2f} ms -- total {(et-st)*1e3:6.2f} ms")
if i == 0:
val = [np.copy(v.numpy()) for v in outs]
buffers = [np.copy(v.numpy().copy()) for v in input_queues.values()]
if test_val is not None:
match = all(np.array_equal(a, b) for a, b in zip(val, test_val, strict=True))
assert match == expect_match, f"outputs {'differ from' if expect_match else 'match'} baseline (seed={seed})"
if test_buffers is not None:
match = all(np.array_equal(a, b) for a, b in zip(buffers, test_buffers, strict=True))
assert match == expect_match, f"buffers {'differ from' if expect_match else 'match'} baseline (seed={seed})"
return val, buffers
print('capture + replay')
test_val, test_buffers = random_inputs_run(jit, SEED)
print('pickle round trip')
jit = pickle.loads(pickle.dumps(jit))
random_inputs_run(jit, SEED, test_val, test_buffers, expect_match=True)
random_inputs_run(jit, SEED+1, test_val, test_buffers, expect_match=False)
return jit
def _captured_devices(jit) -> set[str]:
captured = getattr(jit, 'captured', None)
infos = getattr(captured, 'expected_input_info', None)
if not infos:
return set()
devices: set[str] = set()
for info in infos:
if isinstance(info, tuple) and len(info) >= 4 and isinstance(info[3], str):
devices.add(info[3])
return devices
def _slice_outputs(model_outputs: np.ndarray, output_slices: dict[str, slice]) -> dict[str, np.ndarray]:
return {name: model_outputs[np.newaxis, tensor_slice] for name, tensor_slice in output_slices.items() if name != 'pad'}
def _validate_pose_outputs(parsed_outputs: dict[str, np.ndarray]) -> None:
from openpilot.selfdrive.locationd.locationd import MIN_STD_SANITY_CHECK, ROTATION_SANITY_CHECK, TRANS_SANITY_CHECK
required = (
'pose', 'pose_stds', 'wide_from_device_euler', 'wide_from_device_euler_stds',
'road_transform', 'road_transform_stds',
)
missing = [name for name in required if name not in parsed_outputs]
if missing:
raise AssertionError(f"parsed supercombo outputs missing required odometry tensors: {missing}")
for name in required:
values = parsed_outputs[name]
if not np.isfinite(values).all():
raise AssertionError(f"parsed supercombo output {name} contains non-finite values")
pose = parsed_outputs['pose'][0]
pose_stds = parsed_outputs['pose_stds'][0]
road_transform_stds = parsed_outputs['road_transform_stds'][0]
wide_stds = parsed_outputs['wide_from_device_euler_stds'][0]
if pose_stds.min() <= MIN_STD_SANITY_CHECK:
raise AssertionError(f"pose_stds min {pose_stds.min()} <= {MIN_STD_SANITY_CHECK}")
if road_transform_stds.min() <= MIN_STD_SANITY_CHECK:
raise AssertionError(f"road_transform_stds min {road_transform_stds.min()} <= {MIN_STD_SANITY_CHECK}")
if wide_stds.min() <= MIN_STD_SANITY_CHECK:
raise AssertionError(f"wide_from_device_euler_stds min {wide_stds.min()} <= {MIN_STD_SANITY_CHECK}")
if np.linalg.norm(pose[:3]) > TRANS_SANITY_CHECK:
raise AssertionError(f"pose translation norm {np.linalg.norm(pose[:3])} exceeds {TRANS_SANITY_CHECK}")
if np.linalg.norm(pose[3:]) > ROTATION_SANITY_CHECK:
raise AssertionError(f"pose rotation norm {np.linalg.norm(pose[3:])} exceeds {ROTATION_SANITY_CHECK}")
if np.linalg.norm(pose_stds[:3]) > 10 * TRANS_SANITY_CHECK:
raise AssertionError(
f"pose translation std norm {np.linalg.norm(pose_stds[:3])} exceeds {10 * TRANS_SANITY_CHECK}"
)
if np.linalg.norm(pose_stds[3:]) > 10 * ROTATION_SANITY_CHECK:
raise AssertionError(
f"pose rotation std norm {np.linalg.norm(pose_stds[3:])} exceeds {10 * ROTATION_SANITY_CHECK}"
)
def validate_supercombo_release(run_policy_jit, model_runner, model_metadata, frame_skip, expected_device: str) -> None:
from openpilot.iqpilot.selfdrive.iqmodeld.parser import PhaseParser
direct_fn = make_run_policy(model_runner, model_metadata, frame_skip)
parser = PhaseParser()
queue_factory = partial(make_input_queues, model_metadata['input_shapes'], frame_skip)
image_shape = model_metadata['input_shapes']['img']
jit_queues, jit_npy = queue_factory(Device.DEFAULT)
direct_queues, direct_npy = queue_factory(Device.DEFAULT)
for payload in (jit_npy, direct_npy):
for name, value in payload.items():
value[:] = 0 if value.dtype.kind in ('i', 'u') else 0.0
zero_inputs = {
'warped': Tensor(np.zeros((2, 6, *image_shape[2:]), dtype=np.uint8), device=Device.DEFAULT).realize(),
}
direct_outs, = direct_fn(**{k: direct_queues[k] for k in POLICY_INPUTS}, **zero_inputs)
jit_outs, = run_policy_jit(**{k: jit_queues[k] for k in POLICY_INPUTS}, **zero_inputs)
direct_flat = direct_outs.numpy().astype(np.float32).reshape(-1)
jit_flat = jit_outs.numpy().astype(np.float32).reshape(-1)
if not np.allclose(direct_flat, jit_flat, atol=1e-4, rtol=1e-4):
max_delta = float(np.max(np.abs(direct_flat - jit_flat)))
raise AssertionError(f"JIT supercombo output diverges from direct ONNX execution; max abs delta {max_delta}")
parsed = parser.parse_vision_outputs(_slice_outputs(jit_flat, model_metadata['output_slices']))
_validate_pose_outputs(parsed)
captured_devices = _captured_devices(run_policy_jit)
if expected_device and captured_devices and expected_device not in captured_devices:
raise AssertionError(
f"compiled run_policy backend mismatch: captured {sorted(captured_devices)} expected {expected_device}"
)
def _parse_size(s):
w, h = s.lower().split('x')
return int(w), int(h)
def read_file_chunked_to_shm(path):
from openpilot.common.file_chunker import read_file_chunked
from openpilot.system.hardware.hw import Paths
with tempfile.NamedTemporaryFile(prefix='compile_modeld_', dir=Paths.shm_path(), delete=False) as f:
f.write(read_file_chunked(path))
tmp_path = f.name
atexit.register(lambda: os.path.exists(tmp_path) and os.remove(tmp_path))
return tmp_path
if __name__ == "__main__":
from tinygrad.nn.onnx import OnnxRunner
from openpilot.system.camerad.cameras.nv12_info import get_nv12_info
from openpilot.iqpilot.selfdrive.iqmodeld.metadata import build_metadata_record
p = argparse.ArgumentParser()
p.add_argument('--model-size', type=_parse_size, required=True, help='model input WxH')
p.add_argument('--camera-resolutions', type=_parse_size, nargs='+', required=True,
help='camera resolutions WxH (one or more)')
p.add_argument('--onnx', required=True)
p.add_argument('--output', required=True)
p.add_argument('--frame-skip', type=int, required=True)
p.add_argument('--expected-device', default='QCOM', help='expected tinygrad backend baked into the JIT')
args = p.parse_args()
model_path = read_file_chunked_to_shm(args.onnx)
model_w, model_h = args.model_size
model_runner = OnnxRunner(model_path)
out = {
'metadata': build_metadata_record(model_path),
'frame_skip': args.frame_skip,
}
run_policy_jit = TinyJit(make_run_policy(model_runner, out['metadata'], args.frame_skip), prune=True)
make_policy_queues = partial(make_input_queues, out['metadata']['input_shapes'], args.frame_skip)
make_random_model_inputs = partial(make_random_images, keys=['warped'], shape=(2, 6, *out['metadata']['input_shapes']['img'][2:]))
out['run_policy'] = compile_jit(run_policy_jit, make_random_model_inputs, POLICY_INPUTS,
make_policy_queues)
validate_supercombo_release(out['run_policy'], model_runner, out['metadata'], args.frame_skip, args.expected_device)
for cam_w, cam_h in args.camera_resolutions:
nv12 = NV12Frame(cam_w, cam_h, *get_nv12_info(cam_w, cam_h))
make_random_warp_inputs = partial(make_random_images, keys=['frame', 'big_frame'], shape=nv12.size, device=WARP_DEV)
warp_enqueue = TinyJit(make_warp(nv12, model_w, model_h, args.frame_skip), prune=True)
make_warp_queues = partial(make_warp_input_queues, out['metadata']['input_shapes'], args.frame_skip)
out[(cam_w,cam_h)] = compile_jit(warp_enqueue, make_random_warp_inputs, WARP_INPUTS, make_warp_queues)
captured_devices = _captured_devices(out[(cam_w,cam_h)])
if args.expected_device and captured_devices and args.expected_device not in captured_devices:
raise AssertionError(
f"compiled warp backend mismatch for {cam_w}x{cam_h}: captured {sorted(captured_devices)} expected {args.expected_device}"
)
with open(args.output, "wb") as f:
pickle.dump(out, f)
print(f"Saved JITs to {args.output} ({os.path.getsize(args.output) / 1e6:.2f} MB)")

View File

@@ -0,0 +1,331 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
from __future__ import annotations
import argparse
import atexit
import os
import pickle
import time
from dataclasses import dataclass
from functools import partial
from pathlib import Path
import numpy as np
def _install_firmware_fetch_patch() -> None:
import hashlib
import pathlib
import zstandard
from tinygrad import helpers
if not hasattr(helpers, "fetch_fw"):
return
original_fetch = helpers.fetch_fw
def fetch_fw(path, name, sha256):
archive_path = pathlib.Path(f"/lib/firmware/{path}/{name}.zst")
if archive_path.is_file():
blob = zstandard.ZstdDecompressor().stream_reader(archive_path.read_bytes()).read()
if hashlib.sha256(blob).hexdigest() == sha256:
return blob
return original_fetch(path, name, sha256)
helpers.fetch_fw = fetch_fw
_install_firmware_fetch_patch()
from tinygrad.device import Device
from tinygrad.engine.jit import TinyJit
from tinygrad.helpers import Context
from tinygrad.nn.onnx import OnnxRunner
from tinygrad.tensor import Tensor
@dataclass(frozen=True)
class FrameGeometry:
width: int
height: int
stride: int
y_height: int
uv_height: int
size: int
WARP_INPUT_NAMES = ["img_q", "big_img_q", "tfm", "big_tfm"]
POLICY_INPUT_NAMES = ["feat_q", "desire_q", "desire", "traffic_convention", "action_t"]
WARP_DEV = os.getenv("WARP_DEV")
def _random_tensor_inputs(keys: list[str], shape, device=None):
return {key: Tensor.randint(shape, low=0, high=256, dtype="uint8", device=device).realize() for key in keys}
def _project_frame(src_flat, inverse_matrix, dst_shape, src_shape, stride_pad, border_fill_val=None):
dst_w, dst_h = dst_shape
src_h, src_w = src_shape
x = Tensor.arange(dst_w, device=WARP_DEV).reshape(1, dst_w).expand(dst_h, dst_w).reshape(-1)
y = Tensor.arange(dst_h, device=WARP_DEV).reshape(dst_h, 1).expand(dst_h, dst_w).reshape(-1)
src_x = inverse_matrix[0, 0] * x + inverse_matrix[0, 1] * y + inverse_matrix[0, 2]
src_y = inverse_matrix[1, 0] * x + inverse_matrix[1, 1] * y + inverse_matrix[1, 2]
src_w_scale = inverse_matrix[2, 0] * x + inverse_matrix[2, 1] * y + inverse_matrix[2, 2]
src_x = src_x / src_w_scale
src_y = src_y / src_w_scale
rounded_x = Tensor.round(src_x)
rounded_y = Tensor.round(src_y)
clipped_x = rounded_x.clip(0, src_w - 1).cast("int")
clipped_y = rounded_y.clip(0, src_h - 1).cast("int")
gather_index = clipped_y * (src_w + stride_pad) + clipped_x
sampled = src_flat[gather_index]
if border_fill_val is None:
return sampled
inside = ((rounded_x >= 0) & (rounded_x <= src_w - 1) & (rounded_y >= 0) & (rounded_y <= src_h - 1)).cast(sampled.dtype)
return sampled * inside + Tensor(border_fill_val, dtype=sampled.dtype) * (1 - inside)
def _nv12_to_model_planes(yuv_frame):
y_height = (yuv_frame.shape[0] * 2) // 3
frame_width = yuv_frame.shape[1]
return Tensor.cat(
yuv_frame[0:y_height:2, 0::2],
yuv_frame[1:y_height:2, 0::2],
yuv_frame[0:y_height:2, 1::2],
yuv_frame[1:y_height:2, 1::2],
yuv_frame[y_height:y_height + y_height // 4].reshape((y_height // 2, frame_width // 2)),
yuv_frame[y_height + y_height // 4:y_height + y_height // 2].reshape((y_height // 2, frame_width // 2)),
dim=0,
).reshape((6, y_height // 2, frame_width // 2))
def _warp_kernel_factory(nv12: FrameGeometry, model_w: int, model_h: int):
uv_offset = nv12.stride * nv12.y_height
stride_pad = nv12.stride - nv12.width
def prepare_frame(nv12_blob, inverse_matrix):
inverse_uv = inverse_matrix * Tensor([[1.0, 1.0, 0.5], [1.0, 1.0, 0.5], [2.0, 2.0, 1.0]], device=WARP_DEV)
uv_plane = nv12_blob[uv_offset:uv_offset + nv12.uv_height * nv12.stride].reshape(nv12.uv_height, nv12.stride)
with Context(SPLIT_REDUCEOP=0):
y_plane = _project_frame(nv12_blob[:nv12.height * nv12.stride], inverse_matrix, (model_w, model_h), (nv12.height, nv12.width), stride_pad).realize()
u_plane = _project_frame(uv_plane[:nv12.height // 2, :nv12.width:2].flatten(), inverse_uv, (model_w // 2, model_h // 2), (nv12.height // 2, nv12.width // 2), 0).realize()
v_plane = _project_frame(uv_plane[:nv12.height // 2, 1:nv12.width:2].flatten(), inverse_uv, (model_w // 2, model_h // 2), (nv12.height // 2, nv12.width // 2), 0).realize()
return _nv12_to_model_planes(y_plane.cat(u_plane).cat(v_plane).reshape((model_h * 3 // 2, model_w)))
return prepare_frame
def _vision_queue_state(vision_shapes, frame_skip, device):
img_shape = vision_shapes["img"]
frame_history = img_shape[1] // 6
queue_shape = (frame_skip * (frame_history - 1) + 1, 6, img_shape[2], img_shape[3])
numpy_state = {
"tfm": np.zeros((3, 3), dtype=np.float32),
"big_tfm": np.zeros((3, 3), dtype=np.float32),
}
tensor_state = {
"img_q": Tensor(np.zeros(queue_shape, dtype=np.uint8), device=device).contiguous().realize(),
"big_img_q": Tensor(np.zeros(queue_shape, dtype=np.uint8), device=device).contiguous().realize(),
**{name: Tensor(value, device="NPY").realize() for name, value in numpy_state.items()},
}
return tensor_state, numpy_state
def _policy_queue_state(vision_shapes, policy_shapes, frame_skip, device):
tensor_state, numpy_state = _vision_queue_state(vision_shapes, frame_skip, device)
feature_shape = policy_shapes["features_buffer"]
desire_shape = policy_shapes["desire_pulse"]
traffic_shape = policy_shapes["traffic_convention"]
action_shape = traffic_shape
policy_numpy = {
"desire": np.zeros(desire_shape[2], dtype=np.float32),
"traffic_convention": np.zeros(traffic_shape, dtype=np.float32),
"action_t": np.zeros(action_shape, dtype=np.float32),
}
numpy_state.update(policy_numpy)
tensor_state.update({
"feat_q": Tensor(np.zeros((frame_skip * (feature_shape[1] - 1) + 1, feature_shape[0], feature_shape[2]), 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(),
**{name: Tensor(value, device="NPY").realize() for name, value in policy_numpy.items()},
})
return tensor_state, numpy_state
def _roll_queue(queue_tensor, incoming, sampler):
queue_tensor.assign(queue_tensor[1:].cat(incoming, dim=0).contiguous())
return sampler(queue_tensor)
def _sample_sparse(queue_tensor, frame_skip):
return queue_tensor[::frame_skip].contiguous().flatten(0, 1).unsqueeze(0)
def _sample_desire(queue_tensor, frame_skip):
return queue_tensor.reshape(-1, frame_skip, *queue_tensor.shape[1:]).max(1).flatten(0, 1).unsqueeze(0)
def _build_warp_enqueuer(nv12: FrameGeometry, model_w: int, model_h: int, frame_skip: int):
prepare_frame = _warp_kernel_factory(nv12, model_w, model_h)
sparse_sampler = partial(_sample_sparse, frame_skip=frame_skip)
def 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_main = prepare_frame(frame, tfm).unsqueeze(0).to(Device.DEFAULT)
warped_big = prepare_frame(big_frame, big_tfm).unsqueeze(0).to(Device.DEFAULT)
return (
_roll_queue(img_q, warped_main, sparse_sampler),
_roll_queue(big_img_q, warped_big, sparse_sampler),
)
return enqueue
def _policy_executor(model_runners, model_metadata, frame_skip):
desire_sampler = partial(_sample_desire, frame_skip=frame_skip)
sparse_sampler = partial(_sample_sparse, frame_skip=frame_skip)
hidden_slice = model_metadata["vision"]["output_slices"]["hidden_state"]
def execute(img, big_img, feat_q, desire_q, desire, traffic_convention, action_t):
desire = desire.to(Device.DEFAULT)
traffic_convention = traffic_convention.to(Device.DEFAULT)
action_t = action_t.to(Device.DEFAULT)
Tensor.realize(desire, traffic_convention, action_t)
desire_buffer = _roll_queue(desire_q, desire.reshape(1, 1, -1), desire_sampler)
vision_output = next(iter(model_runners["vision"]({"img": img, "big_img": big_img}).values())).cast("float32")
hidden_state = vision_output[:, hidden_slice].reshape(1, -1).unsqueeze(0)
feature_buffer = _roll_queue(feat_q, hidden_state, sparse_sampler)
on_inputs = {
"features_buffer": feature_buffer,
"desire_pulse": desire_buffer,
"traffic_convention": traffic_convention,
"action_t": action_t,
}
on_output = next(iter(model_runners["on_policy"](on_inputs).values())).cast("float32")
off_output = next(iter(model_runners["off_policy"](on_inputs).values())).cast("float32")
return vision_output, on_output, off_output
return execute
def _replay_and_freeze(jit_runner, random_inputs_factory, queue_keys, queue_factory):
seed = 42
def validate(fn, seed_value, baseline_output=None, baseline_buffers=None, expect_match=True):
queue_tensors, numpy_values = queue_factory(Device.DEFAULT)
np.random.seed(seed_value)
Tensor.manual_seed(seed_value)
replay_count = 1 if (baseline_output is not None or baseline_buffers is not None) else 3
for run_index in range(replay_count):
for value in numpy_values.values():
value[:] = np.random.randn(*value.shape).astype(value.dtype)
Device.default.synchronize()
random_inputs = random_inputs_factory()
start = time.perf_counter()
outputs = fn(**{key: queue_tensors[key] for key in queue_keys}, **random_inputs)
enqueue_done = time.perf_counter()
Device.default.synchronize()
total_done = time.perf_counter()
print(f" [{run_index + 1}/{replay_count}] enqueue {(enqueue_done - start) * 1e3:6.2f} ms -- total {(total_done - start) * 1e3:6.2f} ms")
if run_index == 0:
output_snapshot = [np.copy(value.numpy()) for value in outputs]
buffer_snapshot = [np.copy(value.numpy().copy()) for value in queue_tensors.values()]
if baseline_output is not None:
matches = all(np.array_equal(current, reference) for current, reference in zip(output_snapshot, baseline_output, strict=True))
assert matches == expect_match, f"outputs {'differ from' if expect_match else 'match'} baseline (seed={seed_value})"
if baseline_buffers is not None:
matches = all(np.array_equal(current, reference) for current, reference in zip(buffer_snapshot, baseline_buffers, strict=True))
assert matches == expect_match, f"buffers {'differ from' if expect_match else 'match'} baseline (seed={seed_value})"
return output_snapshot, buffer_snapshot
print("capture + replay")
first_output, first_buffers = validate(jit_runner, seed)
print("pickle round trip")
frozen = pickle.loads(pickle.dumps(jit_runner))
validate(frozen, seed, first_output, first_buffers, expect_match=True)
validate(frozen, seed + 1, first_output, first_buffers, expect_match=False)
return frozen
def _parse_size(text: str) -> tuple[int, int]:
width, height = text.lower().split("x")
return int(width), int(height)
def _read_file_to_shared_memory(path: str) -> str:
from openpilot.common.file_chunker import read_file_chunked
from openpilot.system.hardware.hw import Paths
shm_path = os.path.join(Paths.shm_path(), os.path.basename(path))
atexit.register(lambda: os.path.exists(shm_path) and os.remove(shm_path))
with open(shm_path, "wb") as handle:
handle.write(read_file_chunked(path))
return shm_path
def _arg_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser()
parser.add_argument("--model-size", type=_parse_size, required=True, help="model input WxH")
parser.add_argument("--camera-resolutions", type=_parse_size, nargs="+", required=True, help="camera resolutions WxH (one or more)")
parser.add_argument("--vision-onnx", required=True)
parser.add_argument("--off-policy-onnx", required=True)
parser.add_argument("--on-policy-onnx", required=True)
parser.add_argument("--output", required=True)
parser.add_argument("--frame-skip", type=int, required=True)
return parser
def main(argv: list[str] | None = None) -> int:
from openpilot.iqpilot.selfdrive.iqmodeld.metadata import build_metadata_record
from openpilot.system.camerad.cameras.nv12_info import get_nv12_info
args = _arg_parser().parse_args(argv)
model_w, model_h = args.model_size
model_paths = {
"vision": _read_file_to_shared_memory(args.vision_onnx),
"off_policy": _read_file_to_shared_memory(args.off_policy_onnx),
"on_policy": _read_file_to_shared_memory(args.on_policy_onnx),
}
model_runners = {name: OnnxRunner(path) for name, path in model_paths.items()}
metadata = {name: build_metadata_record(path) for name, path in model_paths.items()}
assert metadata["off_policy"]["input_shapes"] == metadata["on_policy"]["input_shapes"]
output_package: dict = {"metadata": metadata}
policy_jit = TinyJit(_policy_executor(model_runners, metadata, args.frame_skip), prune=True)
policy_queue_factory = partial(_policy_queue_state, metadata["vision"]["input_shapes"], metadata["on_policy"]["input_shapes"], args.frame_skip)
random_model_inputs = partial(_random_tensor_inputs, keys=["img", "big_img"], shape=metadata["vision"]["input_shapes"]["img"])
output_package["run_policy"] = _replay_and_freeze(policy_jit, random_model_inputs, POLICY_INPUT_NAMES, policy_queue_factory)
for cam_w, cam_h in args.camera_resolutions:
nv12 = FrameGeometry(cam_w, cam_h, *get_nv12_info(cam_w, cam_h))
warp_jit = TinyJit(_build_warp_enqueuer(nv12, model_w, model_h, args.frame_skip), prune=True)
warp_queue_factory = partial(_vision_queue_state, metadata["vision"]["input_shapes"], args.frame_skip)
random_warp_inputs = partial(_random_tensor_inputs, keys=["frame", "big_frame"], shape=nv12.size, device=WARP_DEV)
output_package[(cam_w, cam_h)] = _replay_and_freeze(warp_jit, random_warp_inputs, WARP_INPUT_NAMES, warp_queue_factory)
output_package["frame_skip"] = args.frame_skip
with open(args.output, "wb") as handle:
pickle.dump(output_package, handle)
print(f"Saved JITs to {args.output} ({os.path.getsize(args.output) / 1e6:.2f} MB)")
return 0

View File

@@ -0,0 +1,128 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
from __future__ import annotations
import base64
import pickle
import shutil
import sys
from dataclasses import dataclass
from pathlib import Path
import onnx
from openpilot.system.hardware.hw import Paths
_MODEL_STEMS = ("driving_off_policy", "driving_on_policy", "driving_policy", "driving_vision")
@dataclass(frozen=True)
class _ModelBundle:
stem: str
onnx_path: Path
artifact_path: Path
metadata_path: Path
def _tensor_shape(value_info) -> tuple[int, ...]:
return tuple(int(dim.dim_value) for dim in value_info.type.tensor_type.shape.dim)
def _metadata_property(graph_model, key: str) -> str | None:
for property_item in graph_model.metadata_props:
if property_item.key == key:
return property_item.value
return None
def _decode_output_slices(encoded_value: str):
return pickle.loads(base64.b64decode(encoded_value.encode()))
def _metadata_record(graph_model) -> dict:
encoded_slices = _metadata_property(graph_model, "output_slices")
if encoded_slices is None:
raise ValueError("output_slices metadata missing")
return {
"model_checkpoint": _metadata_property(graph_model, "model_checkpoint"),
"output_slices": _decode_output_slices(encoded_slices),
"input_shapes": {item.name: _tensor_shape(item) for item in graph_model.graph.input},
"output_shapes": {item.name: _tensor_shape(item) for item in graph_model.graph.output},
}
def generate_metadata_pkl(model_path, output_path):
try:
graph_model = onnx.load(str(model_path))
metadata = _metadata_record(graph_model)
except Exception:
return False
with open(output_path, "wb") as handle:
pickle.dump(metadata, handle)
return True
def _discover_model_bundles(model_dir: Path) -> list[_ModelBundle]:
bundles: list[_ModelBundle] = []
for stem in _MODEL_STEMS:
onnx_path = model_dir / f"{stem}.onnx"
if not onnx_path.exists():
continue
bundles.append(_ModelBundle(
stem=stem,
onnx_path=onnx_path,
artifact_path=model_dir / f"{stem}_tinygrad.pkl",
metadata_path=model_dir / f"{stem}_metadata.pkl",
))
return bundles
def _prompt_short_name(found_stems: list[str]) -> str | None:
try:
response = input(f"Found models ({', '.join(found_stems)}). Enter model short name (e.g. wmiv4): ").strip()
except EOFError:
return None
return response or None
def _ensure_metadata_file(bundle: _ModelBundle) -> None:
if bundle.metadata_path.exists():
return
generate_metadata_pkl(bundle.onnx_path, bundle.metadata_path)
def _install_bundle(bundle: _ModelBundle, suffix: str, destination_root: Path) -> None:
_ensure_metadata_file(bundle)
renamed_artifact = destination_root / f"{bundle.stem}_{suffix}_tinygrad.pkl"
renamed_metadata = destination_root / f"{bundle.stem}_{suffix}_metadata.pkl"
if bundle.artifact_path.exists():
shutil.move(str(bundle.artifact_path), str(renamed_artifact))
if bundle.metadata_path.exists():
shutil.move(str(bundle.metadata_path), str(renamed_metadata))
def install_models(model_dir):
source_root = Path(model_dir)
bundles = _discover_model_bundles(source_root)
if not bundles:
return
short_name = _prompt_short_name([bundle.stem for bundle in bundles])
if short_name is None:
print("No name provided, skipping installation.")
return
destination_root = Path(Paths.model_root())
destination_root.mkdir(parents=True, exist_ok=True)
for bundle in bundles:
_install_bundle(bundle, short_name, destination_root)
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: install_models_pc.py <model_dir>")
sys.exit(1)
install_models(sys.argv[1])

View File

@@ -0,0 +1,94 @@
/*
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
*/
#include "iqpilot/selfdrive/iqmodeld/transforms/warp_geometry.h"
#include <assert.h>
#include <cstring>
#include "common/clutil.h"
namespace {
void reset_sampler_state(WarpSamplerState *sampler) {
memset(sampler, 0, sizeof(*sampler));
}
void write_projection(cl_command_queue queue, cl_mem dst, const mat3 &projection) {
CL_CHECK(clEnqueueWriteBuffer(queue, dst, CL_TRUE, 0, 3 * 3 * sizeof(float), (void *)projection.v, 0, NULL, NULL));
}
void configure_sample_window(WarpSamplerState *sampler, cl_mem src, int src_stride, int src_px_stride,
int src_offset, int src_rows, int src_cols,
cl_mem dst, int dst_stride, int dst_offset, int dst_rows, int dst_cols,
cl_mem projection_cl) {
CL_CHECK(clSetKernelArg(sampler->bilinear_kernel, 0, sizeof(cl_mem), &src));
CL_CHECK(clSetKernelArg(sampler->bilinear_kernel, 1, sizeof(cl_int), &src_stride));
CL_CHECK(clSetKernelArg(sampler->bilinear_kernel, 2, sizeof(cl_int), &src_px_stride));
CL_CHECK(clSetKernelArg(sampler->bilinear_kernel, 3, sizeof(cl_int), &src_offset));
CL_CHECK(clSetKernelArg(sampler->bilinear_kernel, 4, sizeof(cl_int), &src_rows));
CL_CHECK(clSetKernelArg(sampler->bilinear_kernel, 5, sizeof(cl_int), &src_cols));
CL_CHECK(clSetKernelArg(sampler->bilinear_kernel, 6, sizeof(cl_mem), &dst));
CL_CHECK(clSetKernelArg(sampler->bilinear_kernel, 7, sizeof(cl_int), &dst_stride));
CL_CHECK(clSetKernelArg(sampler->bilinear_kernel, 8, sizeof(cl_int), &dst_offset));
CL_CHECK(clSetKernelArg(sampler->bilinear_kernel, 9, sizeof(cl_int), &dst_rows));
CL_CHECK(clSetKernelArg(sampler->bilinear_kernel, 10, sizeof(cl_int), &dst_cols));
CL_CHECK(clSetKernelArg(sampler->bilinear_kernel, 11, sizeof(cl_mem), &projection_cl));
}
void enqueue_sample_window(cl_command_queue queue, cl_kernel kernel, int width, int height) {
const size_t work_size[2] = {static_cast<size_t>(width), static_cast<size_t>(height)};
CL_CHECK(clEnqueueNDRangeKernel(queue, kernel, 2, NULL, work_size, NULL, 0, 0, NULL));
}
} // namespace
void warp_sampler_init(WarpSamplerState *sampler, cl_context ctx, cl_device_id device_id) {
reset_sampler_state(sampler);
cl_program program_handle = cl_program_from_file(ctx, device_id, TRANSFORM_PATH, "");
sampler->bilinear_kernel = CL_CHECK_ERR(clCreateKernel(program_handle, "projectPlaneBilinear", &err));
CL_CHECK(clReleaseProgram(program_handle));
sampler->full_res_matrix_cl = CL_CHECK_ERR(clCreateBuffer(ctx, CL_MEM_READ_WRITE, 3 * 3 * sizeof(float), NULL, &err));
sampler->half_res_matrix_cl = CL_CHECK_ERR(clCreateBuffer(ctx, CL_MEM_READ_WRITE, 3 * 3 * sizeof(float), NULL, &err));
}
void warp_sampler_release(WarpSamplerState *sampler) {
CL_CHECK(clReleaseMemObject(sampler->full_res_matrix_cl));
CL_CHECK(clReleaseMemObject(sampler->half_res_matrix_cl));
CL_CHECK(clReleaseKernel(sampler->bilinear_kernel));
}
void warp_sampler_dispatch(WarpSamplerState *sampler, cl_command_queue queue,
cl_mem yuv, int in_width, int in_height, int in_stride, int in_uv_offset,
cl_mem out_y, cl_mem out_u, cl_mem out_v,
int out_width, int out_height,
const mat3 &projection) {
const mat3 luma_projection = projection;
const mat3 chroma_projection = transform_scale_buffer(projection, 0.5);
write_projection(queue, sampler->full_res_matrix_cl, luma_projection);
write_projection(queue, sampler->half_res_matrix_cl, chroma_projection);
configure_sample_window(sampler, yuv, in_stride, 1, 0, in_height, in_width,
out_y, out_width, 0, out_height, out_width,
sampler->full_res_matrix_cl);
enqueue_sample_window(queue, sampler->bilinear_kernel, out_width, out_height);
const int chroma_width = in_width / 2;
const int chroma_height = in_height / 2;
const int out_chroma_width = out_width / 2;
const int out_chroma_height = out_height / 2;
const int in_u_offset = in_uv_offset;
const int in_v_offset = in_uv_offset + 1;
configure_sample_window(sampler, yuv, in_stride, 2, in_u_offset, chroma_height, chroma_width,
out_u, out_chroma_width, 0, out_chroma_height, out_chroma_width,
sampler->half_res_matrix_cl);
enqueue_sample_window(queue, sampler->bilinear_kernel, out_chroma_width, out_chroma_height);
configure_sample_window(sampler, yuv, in_stride, 2, in_v_offset, chroma_height, chroma_width,
out_v, out_chroma_width, 0, out_chroma_height, out_chroma_width,
sampler->half_res_matrix_cl);
enqueue_sample_window(queue, sampler->bilinear_kernel, out_chroma_width, out_chroma_height);
}

View File

@@ -0,0 +1,57 @@
/*
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
*/
#define INTER_BITS 5
#define INTER_TAB_SIZE (1 << INTER_BITS)
#define INTER_REMAP_COEF_BITS 15
#define INTER_REMAP_COEF_SCALE (1 << INTER_REMAP_COEF_BITS)
__kernel void projectPlaneBilinear(__global const uchar * src,
int src_row_stride, int src_px_stride, int src_offset, int src_rows, int src_cols,
__global uchar * dst,
int dst_row_stride, int dst_offset, int dst_rows, int dst_cols,
__constant float * M)
{
int dx = get_global_id(0);
int dy = get_global_id(1);
if (dx < dst_cols && dy < dst_rows) {
float x0 = M[0] * dx + M[1] * dy + M[2];
float y0 = M[3] * dx + M[4] * dy + M[5];
float w = M[6] * dx + M[7] * dy + M[8];
w = w != 0.0f ? INTER_TAB_SIZE / w : 0.0f;
int x = rint(x0 * w);
int y = rint(y0 * w);
short sx = convert_short_sat(x >> INTER_BITS);
short sy = convert_short_sat(y >> INTER_BITS);
short min_col = (short)0;
short max_col = convert_short_sat(src_cols - 1);
short min_row = (short)0;
short max_row = convert_short_sat(src_rows - 1);
short sx_clamp = clamp(sx, min_col, max_col);
short sx_p1_clamp = clamp((short)(sx + 1), min_col, max_col);
short sy_clamp = clamp(sy, min_row, max_row);
short sy_p1_clamp = clamp((short)(sy + 1), min_row, max_row);
int top_left = convert_int(src[mad24(sy_clamp, src_row_stride, src_offset + sx_clamp * src_px_stride)]);
int top_right = convert_int(src[mad24(sy_clamp, src_row_stride, src_offset + sx_p1_clamp * src_px_stride)]);
int bottom_left = convert_int(src[mad24(sy_p1_clamp, src_row_stride, src_offset + sx_clamp * src_px_stride)]);
int bottom_right = convert_int(src[mad24(sy_p1_clamp, src_row_stride, src_offset + sx_p1_clamp * src_px_stride)]);
short ay = (short)(y & (INTER_TAB_SIZE - 1));
short ax = (short)(x & (INTER_TAB_SIZE - 1));
float taby = 1.f / INTER_TAB_SIZE * ay;
float tabx = 1.f / INTER_TAB_SIZE * ax;
int coeff0 = convert_short_sat_rte((1.0f - taby) * (1.0f - tabx) * INTER_REMAP_COEF_SCALE);
int coeff1 = convert_short_sat_rte((1.0f - taby) * tabx * INTER_REMAP_COEF_SCALE);
int coeff2 = convert_short_sat_rte(taby * (1.0f - tabx) * INTER_REMAP_COEF_SCALE);
int coeff3 = convert_short_sat_rte(taby * tabx * INTER_REMAP_COEF_SCALE);
int blended = top_left * coeff0 + top_right * coeff1 + bottom_left * coeff2 + bottom_right * coeff3;
int dst_index = mad24(dy, dst_row_stride, dst_offset + dx);
dst[dst_index] = convert_uchar_sat((blended + (1 << (INTER_REMAP_COEF_BITS - 1))) >> INTER_REMAP_COEF_BITS);
}
}

View File

@@ -0,0 +1,28 @@
/*
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
*/
#pragma once
#define CL_USE_DEPRECATED_OPENCL_1_2_APIS
#ifdef __APPLE__
#include <OpenCL/cl.h>
#else
#include <CL/cl.h>
#endif
#include "common/mat.h"
struct WarpSamplerState {
cl_kernel bilinear_kernel;
cl_mem full_res_matrix_cl;
cl_mem half_res_matrix_cl;
};
void warp_sampler_init(WarpSamplerState *sampler, cl_context ctx, cl_device_id device_id);
void warp_sampler_release(WarpSamplerState *sampler);
void warp_sampler_dispatch(WarpSamplerState *sampler, cl_command_queue queue,
cl_mem yuv, int in_width, int in_height, int in_stride, int in_uv_offset,
cl_mem out_y, cl_mem out_u, cl_mem out_v,
int out_width, int out_height,
const mat3 &projection);

View File

@@ -0,0 +1,82 @@
/*
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
*/
#include "iqpilot/selfdrive/iqmodeld/transforms/yuv.h"
#include <assert.h>
#include <cstdio>
#include <cstring>
namespace {
void clear_kernel_bundle(PackedFrameKernels *kernels) {
memset(kernels, 0, sizeof(*kernels));
}
void bind_kernel_bundle(PackedFrameKernels *kernels, cl_program cl_program_handle) {
kernels->y_pair_kernel = CL_CHECK_ERR(clCreateKernel(cl_program_handle, "packLumaHalves", &err));
kernels->uv_lane_kernel = CL_CHECK_ERR(clCreateKernel(cl_program_handle, "packChromaPlane", &err));
kernels->span_copy_kernel = CL_CHECK_ERR(clCreateKernel(cl_program_handle, "copyPlaneBytes", &err));
}
void launch_linear_kernel(cl_command_queue queue, cl_kernel kernel, size_t work_items) {
CL_CHECK(clEnqueueNDRangeKernel(queue, kernel, 1, nullptr, &work_items, nullptr, 0, 0, nullptr));
}
} // namespace
void packed_frame_kernels_init(PackedFrameKernels *kernels, cl_context ctx, cl_device_id device_id, int width, int height) {
clear_kernel_bundle(kernels);
kernels->raster_width = width;
kernels->raster_height = height;
char compiler_args[1024];
snprintf(compiler_args, sizeof(compiler_args),
"-cl-fast-relaxed-math -cl-denorms-are-zero "
"-DTRANSFORMED_WIDTH=%d -DTRANSFORMED_HEIGHT=%d",
width, height);
cl_program program_handle = cl_program_from_file(ctx, device_id, LOADYUV_PATH, compiler_args);
bind_kernel_bundle(kernels, program_handle);
CL_CHECK(clReleaseProgram(program_handle));
}
void packed_frame_kernels_release(PackedFrameKernels *kernels) {
CL_CHECK(clReleaseKernel(kernels->y_pair_kernel));
CL_CHECK(clReleaseKernel(kernels->uv_lane_kernel));
CL_CHECK(clReleaseKernel(kernels->span_copy_kernel));
}
void packed_frame_emit(PackedFrameKernels *kernels, cl_command_queue queue,
cl_mem y_plane_cl, cl_mem u_plane_cl, cl_mem v_plane_cl,
cl_mem packed_frame_cl) {
cl_int output_offset = 0;
const size_t luma_work_items = (kernels->raster_width * kernels->raster_height) / 8;
const size_t chroma_work_items = ((kernels->raster_width / 2) * (kernels->raster_height / 2)) / 8;
CL_CHECK(clSetKernelArg(kernels->y_pair_kernel, 0, sizeof(cl_mem), &y_plane_cl));
CL_CHECK(clSetKernelArg(kernels->y_pair_kernel, 1, sizeof(cl_mem), &packed_frame_cl));
CL_CHECK(clSetKernelArg(kernels->y_pair_kernel, 2, sizeof(cl_int), &output_offset));
launch_linear_kernel(queue, kernels->y_pair_kernel, luma_work_items);
output_offset += kernels->raster_width * kernels->raster_height;
CL_CHECK(clSetKernelArg(kernels->uv_lane_kernel, 0, sizeof(cl_mem), &u_plane_cl));
CL_CHECK(clSetKernelArg(kernels->uv_lane_kernel, 1, sizeof(cl_mem), &packed_frame_cl));
CL_CHECK(clSetKernelArg(kernels->uv_lane_kernel, 2, sizeof(cl_int), &output_offset));
launch_linear_kernel(queue, kernels->uv_lane_kernel, chroma_work_items);
output_offset += (kernels->raster_width / 2) * (kernels->raster_height / 2);
CL_CHECK(clSetKernelArg(kernels->uv_lane_kernel, 0, sizeof(cl_mem), &v_plane_cl));
CL_CHECK(clSetKernelArg(kernels->uv_lane_kernel, 1, sizeof(cl_mem), &packed_frame_cl));
CL_CHECK(clSetKernelArg(kernels->uv_lane_kernel, 2, sizeof(cl_int), &output_offset));
launch_linear_kernel(queue, kernels->uv_lane_kernel, chroma_work_items);
}
void packed_frame_clone_range(PackedFrameKernels *kernels, cl_command_queue queue, cl_mem src, cl_mem dst,
size_t src_offset, size_t dst_offset, size_t size) {
CL_CHECK(clSetKernelArg(kernels->span_copy_kernel, 0, sizeof(cl_mem), &src));
CL_CHECK(clSetKernelArg(kernels->span_copy_kernel, 1, sizeof(cl_mem), &dst));
CL_CHECK(clSetKernelArg(kernels->span_copy_kernel, 2, sizeof(cl_int), &src_offset));
CL_CHECK(clSetKernelArg(kernels->span_copy_kernel, 3, sizeof(cl_int), &dst_offset));
launch_linear_kernel(queue, kernels->span_copy_kernel, size / 8);
}

View File

@@ -0,0 +1,46 @@
/*
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
*/
#define UV_SIZE ((TRANSFORMED_WIDTH/2)*(TRANSFORMED_HEIGHT/2))
__kernel void packLumaHalves(__global uchar8 const * const in_luma,
__global uchar * out_frame,
int out_offset)
{
const int gid = get_global_id(0);
const int output_index_start = gid * 8;
const int row = output_index_start / TRANSFORMED_WIDTH;
const int col = output_index_start % TRANSFORMED_WIDTH;
const uchar8 luma_block = in_luma[gid];
__global uchar *top_or_left;
__global uchar *bottom_or_right;
if ((row & 1) == 0) {
top_or_left = out_frame + out_offset;
bottom_or_right = out_frame + out_offset + UV_SIZE * 2;
} else {
top_or_left = out_frame + out_offset + UV_SIZE;
bottom_or_right = out_frame + out_offset + UV_SIZE * 3;
}
const int row_stride = (row / 2) * (TRANSFORMED_WIDTH / 2) + col / 2;
vstore4(luma_block.s0246, 0, top_or_left + row_stride);
vstore4(luma_block.s1357, 0, bottom_or_right + row_stride);
}
__kernel void packChromaPlane(__global uchar8 const * const in_plane,
__global uchar8 * out_frame,
int out_offset)
{
const int gid = get_global_id(0);
out_frame[gid + out_offset / 8] = in_plane[gid];
}
__kernel void copyPlaneBytes(__global uchar8 * in_plane,
__global uchar8 * out_plane,
int in_offset,
int out_offset)
{
const int gid = get_global_id(0);
out_plane[gid + out_offset / 8] = in_plane[gid + in_offset / 8];
}

View File

@@ -0,0 +1,24 @@
/*
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
*/
#pragma once
#include "common/clutil.h"
struct PackedFrameKernels {
int raster_width;
int raster_height;
cl_kernel y_pair_kernel;
cl_kernel uv_lane_kernel;
cl_kernel span_copy_kernel;
};
void packed_frame_kernels_init(PackedFrameKernels *kernels, cl_context ctx, cl_device_id device_id, int width, int height);
void packed_frame_kernels_release(PackedFrameKernels *kernels);
void packed_frame_emit(PackedFrameKernels *kernels, cl_command_queue queue,
cl_mem y_plane_cl, cl_mem u_plane_cl, cl_mem v_plane_cl,
cl_mem packed_frame_cl);
void packed_frame_clone_range(PackedFrameKernels *kernels, cl_command_queue queue, cl_mem src, cl_mem dst,
size_t src_offset, size_t dst_offset, size_t size);