IQ.Pilot Release Commit @ b6534c0

This commit is contained in:
IQ.Lvbs CI [bot]
2026-08-27 20:17:33 -05:00
commit 00f07cac48
4706 changed files with 1257146 additions and 0 deletions

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

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

View File

@@ -0,0 +1,127 @@
"""
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", "tinygrad_dir")
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():
return [path for path in glob.glob(tinygrad_dir + "/**", recursive=True) 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):
fn = File(f"models/{model_name}").abspath
return lenv.Command(
fn + "_tinygrad.pkl",
[fn + ".onnx"] + tinygrad_files,
lenv.PrettyAction(
f'${{PYWARN}} {flags} python3 {Dir("#iqpilot/selfdrive/iqmodeld/tools").abspath}/compile_model.py {fn}.onnx {fn}_tinygrad.pkl',
'MODEL', logfile='${TARGET}.log')
)
for model_name in present_models:
tg_compile(_tinygrad_flags(), model_name)
from iqpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye
from iqpilot.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):
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}} {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,9 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
from iqpilot._proprietary_loader import ProprietaryModuleMissing, load_private_module
try:
load_private_module(__name__, "iqpilot_private.models.big_catalog")
except ProprietaryModuleMissing:
from iqpilot.models_private_src.big_catalog import *

View File

@@ -0,0 +1,70 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
import numpy as np
from iqpilot.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:
from iqpilot.selfdrive.locationd.calibrationd import HEIGHT_SANE_MIN, HEIGHT_SANE_MAX
h = sm["extrinsicsCalibration"].height[0] if sm["extrinsicsCalibration"].height else 1.22
return h if HEIGHT_SANE_MIN <= h <= HEIGHT_SANE_MAX 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,133 @@
"""
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 index_function(index: int, max_val: float = 192, max_idx: int = 32) -> float:
return max_val * ((index / max_idx) ** 2)
def _quadratic_series(limit: float, steps: int) -> list[float]:
return [index_function(index, max_val=limit, max_idx=steps - 1) 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,766 @@
#!/usr/bin/env python3
from __future__ import annotations
import sys
import time
from dataclasses import dataclass
from typing import Any
import iqpilot.cereal.messaging as messaging
import numpy as np
from iqpilot.cereal import car, custom, log
from iqpilot.cereal.messaging import PubMaster, SubMaster
from iqpilot.cereal.visionipc import VisionStreamType
from msgq.visionipc import VisionBuf, VisionIpcClient
from iqdbc.car.car_helpers import get_demo_car_params
from setproctitle import setproctitle
from iqpilot.common.filter_simple import FirstOrderFilter
from iqpilot.common.iq_perf import PerfSample, PerfTraceEmitter, PerfTraceRing
from iqpilot.common.params import Params
from iqpilot.common.realtime import DT_MDL, config_realtime_process
from iqpilot.common.swaglog import cloudlog
from iqpilot.common.transformations.camera import DEVICE_CAMERAS
from iqpilot.common.transformations.model import get_warp_matrix
from iqpilot.selfdrive.controls.lib.desire_helper import DesireHelper
from iqpilot.selfdrive.controls.lib.drive_helpers import (
MODEL_SMOOTHING_MAX_TOTAL_SEC,
dynamic_lat_smooth_extra_seconds,
get_accel_from_plan,
smooth_value,
)
from iqpilot.selfdrive.locationd.calibration_helpers import get_calibrated_rpy
from iqpilot.system import sentry
from iqpilot.common.steer_delay import lateral_action_delay
from iqpilot.selfdrive.iqmodeld.models.helpers import get_active_bundle
from iqpilot.selfdrive.iqmodeld.models.inference_state import InferenceStateBase
from iqpilot.selfdrive.iqmodeld.models.runners.model_runner import get_model_runner
from iqpilot.selfdrive.iqmodeld.camera import CameraOffsetHelper
from iqpilot.selfdrive.iqmodeld.config import Plan
from iqpilot.selfdrive.iqmodeld.messaging import (
DrivePacketMemory,
pick_curvature,
populate_drive_messages,
populate_odometry_message,
)
from iqpilot.selfdrive.iqmodeld.metadata import select_meta_layout
try:
from iqpilot.selfdrive.iqmodeld.native.iqmodel_pyx import RoadProjector, WarpContext
except ModuleNotFoundError:
class WarpContext:
def __init__(self, *args, **kwargs):
raise ModuleNotFoundError("iqpilot.selfdrive.iqmodeld.native.iqmodel_pyx is not built")
class RoadProjector:
def __init__(self, *args, **kwargs):
raise ModuleNotFoundError("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
_FRAME_STARVED_BACKOFF_POLLS = 5
_FRAME_STARVED_BACKOFF_SECONDS = 0.005
_FRAME_STARVED_LOG_EVERY = 200
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["extrinsicsCalibration"] and sm.seen["roadCameraState"] and sm.seen["deviceState"]):
return self.main_warp, self.extra_warp, self.ready
rpy = get_calibrated_rpy(sm["extrinsicsCalibration"])
if rpy is None:
live_calib = sm["extrinsicsCalibration"]
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, channel_path: str | None = None):
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._channel = None
if channel_path is not None:
from iqpilot.selfdrive.iqmodeld.model_channel import ModelChannel
self._channel = ModelChannel(channel_path, create=True)
self._cameras = CameraIngress(self._gpu)
pub_services = ["iqPerfTrace"] if self._channel is not None else [
"modelV2", "drivingModelData", "cameraOdometry", "iqDriveModelData", "iqPerfTrace"]
self._pub = PubMaster(pub_services)
self._sub = SubMaster([
"deviceState", "carState", "roadCameraState", "extrinsicsCalibration",
"driverMonitoringState", "carControl", "lateralDelay", "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
from iqpilot.selfdrive.iqmodeld.egpu_helpers import egpu_selected
big_enabled = self._params.get_bool("IQEmacEnabled") or egpu_selected(self._params)
if big_enabled != (self._channel is not None):
# publish mode is fixed at startup: staying up would fight the selector for modelV2
cloudlog.warning("iqmodeld: big backend toggled, restarting to switch publish mode")
sys.exit(0)
self._runtime.lat_delay = lateral_action_delay(self._params, self._car_params, self._sub["lateralDelay"].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,
)
if self._channel is not None:
self._channel.write(main_stamp.frame_id, {
"source": "small",
"frame_id": main_stamp.frame_id,
"timestamp_sof": int(main_stamp.timestamp_sof),
"live_calib_seen": bool(live_calib_seen),
"model_execution_time": float(execution_time),
"msgs": {
"modelV2": model_msg.to_bytes(),
"drivingModelData": driving_msg.to_bytes(),
"cameraOdometry": pose_msg.to_bytes(),
"iqDriveModelData": iq_msg.to_bytes(),
},
})
return
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
starved_polls = 0
while True:
frame_pair = self._cameras.pull()
if frame_pair is None:
starved_polls += 1
if starved_polls >= _FRAME_STARVED_BACKOFF_POLLS:
time.sleep(_FRAME_STARVED_BACKOFF_SECONDS)
if starved_polls % _FRAME_STARVED_LOG_EVERY == 0:
cloudlog.error(f"visionipc delivered no frames for {starved_polls} polls; model is not running")
continue
if starved_polls:
cloudlog.warning(f"visionipc recovered after {starved_polls} frameless polls")
starved_polls = 0
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, channel_path: str | None = "auto"):
if channel_path == "auto":
channel_path = None
params = Params()
from iqpilot.selfdrive.iqmodeld.egpu_helpers import egpu_selected
if params.get_bool("IQEmacEnabled") or egpu_selected(params):
from iqpilot.selfdrive.iqmodeld.model_channel import SMALL_CHANNEL
channel_path = SMALL_CHANNEL
InferenceDaemon(demo=demo, channel_path=channel_path).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": "6bcf9668455c022ccaa6443ed2daddc5b4348ffbdf68dd5abd9c37a1b17bfe98",
"uri": "https://git.konn3kt.com/teal/IQModels/raw/branch/main/models/recompiled18/model-C210M/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/recompiled18/model-C210M/driving_policy_c210m_metadata.pkl"
},
"fileName": "driving_policy_c210m_metadata.pkl"
},
"type": "policy"
},
{
"artifact": {
"downloadUri": {
"sha256": "10fd116056fd1790553c20e09b2b640ebcb85e72418fc9685e0800a513bbe950",
"uri": "https://git.konn3kt.com/teal/IQModels/raw/branch/main/models/recompiled18/model-C210M/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/recompiled18/model-C210M/driving_vision_c210m_metadata.pkl"
},
"fileName": "driving_vision_c210m_metadata.pkl"
},
"type": "vision"
}
],
"overrides": [
{
"key": "folder",
"value": "Comma Models"
},
{
"key": "lat",
"value": ".0"
},
{
"key": "long",
"value": ".3"
}
],
"ref": "default",
"runner": "tinygrad",
"status": "notDownloading"
}

View File

@@ -0,0 +1,46 @@
"""
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 iqpilot.cereal import log
from iqpilot.selfdrive.controls.lib.drive_helpers import smooth_value
LAT_SMOOTH_SECONDS = 0.0
LONG_SMOOTH_SECONDS = 0.3
MIN_LAT_CONTROL_SPEED = 0.3
DESIRE_LEN = 8
def get_action_from_model(outputs: dict[str, np.ndarray], prev_action: log.ModelDataV2.Action,
v_ego: float, lat_action_t: float, long_action_t: float,
lat_smooth_seconds: float | None = None) -> log.ModelDataV2.Action:
if "action" in outputs:
desired_accel = float(outputs["action"][0, 1])
desired_curvature = float(outputs["action"][0, 0]) / (max(1.0, v_ego)) ** 2
should_stop = bool(v_ego < 0.3 and desired_accel < 0.1)
else:
from iqpilot.selfdrive.controls.lib.drive_helpers import get_accel_from_plan, get_curvature_from_plan
from iqpilot.selfdrive.iqmodeld.config import ModelConstants, Plan
plan = outputs["plan"][0]
desired_accel, should_stop = get_accel_from_plan(plan[:, Plan.VELOCITY][:, 0],
plan[:, Plan.ACCELERATION][:, 0],
ModelConstants.T_IDXS,
action_t=long_action_t)
desired_curvature = get_curvature_from_plan(plan[:, Plan.T_FROM_CURRENT_EULER][:, 2],
plan[:, Plan.ORIENTATION_RATE][:, 2],
ModelConstants.T_IDXS, v_ego, lat_action_t)
desired_accel, should_stop = float(desired_accel), bool(should_stop)
desired_curvature = float(desired_curvature)
desired_accel = smooth_value(desired_accel, prev_action.desiredAcceleration, LONG_SMOOTH_SECONDS)
if v_ego > MIN_LAT_CONTROL_SPEED:
lat_smooth = LAT_SMOOTH_SECONDS if lat_smooth_seconds is None else lat_smooth_seconds
desired_curvature = smooth_value(desired_curvature, prev_action.desiredCurvature, lat_smooth)
else:
desired_curvature = prev_action.desiredCurvature
return log.ModelDataV2.Action(desiredCurvature=float(desired_curvature),
desiredAcceleration=float(desired_accel),
shouldStop=should_stop)

View File

@@ -0,0 +1,158 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
from __future__ import annotations
import hashlib
import json
import os
import urllib.request
from pathlib import Path
from iqpilot.system.hardware.usb import egpu_dock_ready
USB_SYSFS_ROOT = "/sys/bus/usb/devices"
COMMA_LFS_BATCH_URL = "https://gitlab.com/commaai/openpilot-lfs.git/info/lfs/objects/batch"
DOWNLOAD_CHUNK = 4 * 1024 * 1024
def usbgpu_present(sysfs_root: str = USB_SYSFS_ROOT) -> bool:
return egpu_dock_ready(Path(sysfs_root))
def egpu_present_consented(params, sysfs_root: str = USB_SYSFS_ROOT) -> bool:
try:
if params is not None and params.get_bool("IQEgpuDisabled"):
return False
except Exception:
pass
return usbgpu_present(sysfs_root)
def egpu_selected(params, sysfs_root: str = USB_SYSFS_ROOT) -> bool:
try:
if params is not None and params.get_bool("IQEgpuDisabled"):
return False
if params is not None and params.get_bool("IQEgpuEnabled"):
return True
except Exception:
pass
return usbgpu_present(sysfs_root)
def resolve_backend(emac_enabled: bool, egpu_enabled: bool, egpu_present: bool = False) -> str | None:
if egpu_present:
return "egpu"
if emac_enabled:
return "emac"
if egpu_enabled:
return "egpu"
return None
def egpu_pkl_path(meta: dict) -> str:
from iqpilot.system.hardware.hw import Paths
return os.path.join(Paths.model_root(), f"egpu_{meta['key']}_{meta['sha256'][:8]}_amd_tinygrad.pkl")
def onnx_cache_path(meta: dict) -> str:
from iqpilot.system.hardware.hw import Paths
return os.path.join(Paths.model_root(), f"{meta['model_name']}_{meta['sha256'][:8]}.onnx")
def _sha256_file(path: str) -> str:
digest = hashlib.sha256()
with open(path, "rb") as f:
while chunk := f.read(DOWNLOAD_CHUNK):
digest.update(chunk)
return digest.hexdigest()
def quarantine_artifact(path: str, why: str) -> None:
try:
if os.path.isfile(path):
os.replace(path, path + ".unusable")
except OSError:
try:
os.remove(path)
except OSError:
pass
def local_onnx(meta: dict) -> str | None:
path = onnx_cache_path(meta)
if not os.path.isfile(path):
return None
size = int(meta.get("download", {}).get("size", 0))
if size and os.path.getsize(path) != size:
quarantine_artifact(path, "onnx size mismatch")
return None
if _sha256_file(path) != meta["sha256"]:
quarantine_artifact(path, "onnx sha256 mismatch")
return None
return path
def resolve_download_url(download_url: str, sha256: str, size: int, timeout: float = 30.0) -> str:
if download_url.startswith("commalfs:"):
oid = download_url.split(":", 1)[1]
body = json.dumps({"operation": "download", "transfers": ["basic"],
"objects": [{"oid": oid, "size": size}]}).encode()
req = urllib.request.Request(COMMA_LFS_BATCH_URL, data=body, headers={
"Accept": "application/vnd.git-lfs+json", "Content-Type": "application/vnd.git-lfs+json"})
with urllib.request.urlopen(req, timeout=timeout) as r:
d = json.load(r)
return d["objects"][0]["actions"]["download"]["href"]
return download_url
def download_onnx(meta: dict, progress_cb=None) -> str:
from iqpilot.selfdrive.iqmodeld.egpu_model import download_descriptor
download_url, size = download_descriptor(meta)
if not download_url:
raise RuntimeError(f"model {meta['key']} has no download source; stage the onnx at {onnx_cache_path(meta)}")
url = resolve_download_url(download_url, meta["sha256"], size)
path = onnx_cache_path(meta)
os.makedirs(os.path.dirname(path), exist_ok=True)
tmp = path + ".part"
digest = hashlib.sha256()
got = 0
with urllib.request.urlopen(url, timeout=60) as r, open(tmp, "wb") as f:
while chunk := r.read(DOWNLOAD_CHUNK):
f.write(chunk)
digest.update(chunk)
got += len(chunk)
if progress_cb is not None and size:
progress_cb(got / size)
if size and got != size:
os.remove(tmp)
raise RuntimeError(f"onnx download truncated: {got}/{size} bytes")
if digest.hexdigest() != meta["sha256"]:
os.remove(tmp)
raise RuntimeError(f"onnx sha256 mismatch for {meta['key']}")
os.replace(tmp, path)
return path
def patch_tinygrad_fetch_fw() -> None:
import pathlib
import zstandard
from tinygrad import helpers
if getattr(helpers.fetch_fw, "_iq_patched", False):
return
_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)
fetch_fw._iq_patched = True
helpers.fetch_fw = fetch_fw

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
EGPU_MODELS: dict[str, dict] = {
"lebrowski": {
"model_name": "big_driving_supercombo",
"sha256": "a501760a9d1d5fef0eab2b8c5d122d06124fc26dc8e0782e0aa94b82a208f0ff",
"output_len": 2580,
"frame_skip": 4,
"download": {"kind": "comma_lfs", "size": 1757355221},
"input_shapes": {
"img": (1, 12, 128, 256),
"big_img": (1, 12, 128, 256),
"desire_pulse": (1, 25, 8),
"traffic_convention": (1, 2),
"action_t": (1, 2),
"features_buffer": (1, 24, 512),
},
"output_slices": {
"lane_lines": slice(0, 528),
"lane_lines_prob": slice(528, 536),
"road_edges": slice(536, 800),
"meta": slice(800, 855),
"desire_pred": slice(855, 887),
"pose": slice(887, 899),
"wide_from_device_euler": slice(899, 905),
"road_transform": slice(905, 917),
"plan": slice(917, 1907),
"lead": slice(1907, 2051),
"lead_prob": slice(2051, 2054),
"desire_state": slice(2054, 2062),
"action": slice(2062, 2066),
"hidden_state": slice(2066, 2578),
"pad": slice(-2, None),
},
},
"comma_small": {
"model_name": "driving_supercombo",
"sha256": "659727c4d4839adc4992a254409a54259a8756a743f2d567bf5fdc6579f8009b",
"output_len": 2576,
"frame_skip": 4,
"download": {"kind": "comma_lfs", "size": 60881999},
"output_slices": {
"meta": slice(0, 55),
"desire_pred": slice(55, 87),
"pose": slice(87, 99),
"wide_from_device_euler": slice(99, 105),
"road_transform": slice(105, 117),
"lane_lines": slice(117, 645),
"lane_lines_prob": slice(645, 653),
"road_edges": slice(653, 917),
"lead": slice(917, 1061),
"lead_prob": slice(1061, 1064),
"hidden_state": slice(1064, 1576),
"plan": slice(1576, 2566),
"desire_state": slice(2566, 2574),
"pad": slice(-2, None),
},
},
}
DEFAULT_EGPU_MODEL = "lebrowski"
BIG_MODEL_NAME = "big_driving_supercombo"
def get_egpu_model(key: str | None = None) -> dict:
name = key or DEFAULT_EGPU_MODEL
if name not in EGPU_MODELS:
raise KeyError(f"unknown eGPU model {name!r}; known: {sorted(EGPU_MODELS)}")
return {"key": name, **EGPU_MODELS[name]}
def resolve_egpu_model(params, selected: str | bytes | None = None, allow_refresh: bool = True) -> dict | None:
name = selected if selected is not None else (params.get("IQEmacModel") if params is not None else None)
name = name.decode() if isinstance(name, bytes) else (name or "")
if not name or name == DEFAULT_EGPU_MODEL:
return get_egpu_model()
from iqpilot.selfdrive.iqmodeld.big_catalog import cached_catalog, refresh_catalog
catalog = cached_catalog(params)
if name not in catalog and params is not None and allow_refresh:
refresh_catalog(params)
catalog = cached_catalog(params)
meta = catalog.get(name)
if meta is None or meta.get("model_name") != BIG_MODEL_NAME:
return None
return {"key": name, **meta}
def download_descriptor(meta: dict) -> tuple[str, int]:
dl = meta.get("download")
if not dl:
return "", 0
if dl["kind"] == "comma_lfs":
return f"commalfs:{meta['sha256']}", int(dl["size"])
if dl["kind"] == "url":
return str(dl["url"]), int(dl.get("size", 0))
return "", 0

View File

@@ -0,0 +1,47 @@
"""
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 iqpilot.selfdrive.iqmodeld.temporal_state import MODEL_INPUT_SPEC, TemporalInputState, spec_from_meta
class EgpuPipelineError(RuntimeError):
pass
class EgpuPipeline:
def __init__(self, meta: dict, infer_fn):
if meta.get("split"):
raise EgpuPipelineError(f"model {meta['key']} is a split model; eGPU v1 runs fused models only")
self.meta = meta
self.infer_fn = infer_fn
self.state = TemporalInputState(meta["frame_skip"], spec_from_meta(meta) or MODEL_INPUT_SPEC)
self.hidden_slice = meta["output_slices"]["hidden_state"]
self.output_len = int(meta["output_len"])
def run(self, warped: np.ndarray, desire_vec: np.ndarray, traffic_convention: np.ndarray,
action_t: np.ndarray) -> np.ndarray:
inputs = self.state.push_and_materialize(warped, desire_vec, traffic_convention, action_t)
out = np.asarray(self.infer_fn(inputs), dtype=np.float32).reshape(-1)
if out.shape[0] != self.output_len:
raise EgpuPipelineError(f"eGPU output length {out.shape[0]} != {self.output_len}")
if not np.isfinite(out).all():
raise EgpuPipelineError("eGPU output contains non-finite values")
self.state.note_hidden_state(out, self.hidden_slice)
return out
def make_big_channel_payload(frame_id: int, live_calib_seen: bool, execution_time: float,
egpu_exec_ms: float, msgs: dict[str, bytes]) -> dict:
return {
"source": "egpu_big",
"frame_id": int(frame_id),
"live_calib_seen": bool(live_calib_seen),
"model_execution_time": float(execution_time),
"egpu_exec_ms": float(egpu_exec_ms),
"msgs": msgs,
}

View File

@@ -0,0 +1,106 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
from __future__ import annotations
import struct
from iqpilot.cereal import messaging
from iqpilot.common.swaglog import cloudlog
METRICS_REFRESH_EVERY = 100
class EgpuDockTelemetry:
def __init__(self, pm, big: bool):
self.pm = pm
self.big = big
self.valid = True
self.sends = 0
self.metrics: dict[str, float] = {}
self._power_limit: int | None = None
self._asm_usb = None
def _device(self):
from tinygrad.device import Device
return Device
def _open_asm_usb(self):
import usb1
from iqpilot.system.hardware.usb import EGPU_DOCK_USB_IDS
context = usb1.USBContext()
for vendor_id, product_id in EGPU_DOCK_USB_IDS:
handle = context.openByVendorIDAndProductID(vendor_id, product_id, skip_on_error=True)
if handle is not None:
return handle
context.close()
return None
def _read_ina(self):
Device = self._device()
if "AMD" in Device._opened_devices and self._asm_usb is None:
try:
raw = Device["AMD"].iface.pci_dev.usb.usb.control_read(0xC0, 5)
return struct.unpack("<Hh?", bytes(raw))
except Exception:
pass
if self._asm_usb is None:
self._asm_usb = self._open_asm_usb()
if self._asm_usb is None:
raise RuntimeError("no egpu ASM usb handle")
try:
raw = self._asm_usb.controlRead(0xC0, 0xC0, 0, 0, 5, timeout=100)
except Exception:
self._asm_usb = None
raise
return struct.unpack("<Hh?", bytes(raw))
def power_limit(self, smu) -> int:
if self._power_limit is None:
self._power_limit = smu._send_msg(smu.smu_mod.PPSMC_MSG_GetPptLimit, 0, read_back_arg=True, timeout=100)
return self._power_limit
def send(self) -> None:
Device = self._device()
msg = messaging.new_message("egpuDockState")
state = msg.egpuDockState
self.sends += 1
if self.big and "AMD" in Device._opened_devices and self.sends % METRICS_REFRESH_EVERY == 1:
try:
smu = Device["AMD"].iface.dev_impl.smu
smu._send_msg(smu.smu_mod.PPSMC_MSG_TransferTableSmu2Dram, smu.smu_mod.TABLE_SMU_METRICS, timeout=100)
metrics = smu.read_table(smu.smu_mod.SmuMetricsExternal_t, smu.smu_mod.TABLE_SMU_METRICS).SmuMetrics
self.metrics = {"tempC": metrics.AvgTemperature[smu.smu_mod.TEMP_HOTSPOT],
"memoryTempC": metrics.AvgTemperature[smu.smu_mod.TEMP_MEM],
"powerDrawW": metrics.AverageSocketPower,
"powerLimitW": self.power_limit(smu),
"gpuUsagePercent": metrics.AverageGfxActivity,
"gpuClockMhz": metrics.AverageGfxclkFrequencyPostDs,
"fanSpeedRpm": metrics.AvgFanRpm}
self.valid = True
except Exception:
if self.valid:
cloudlog.exception("egpu dock state read failed")
self.valid = False
self.metrics.clear()
if self.big:
for k, v in self.metrics.items():
setattr(state, k, v)
asm_valid = False
try:
state.supplyVoltage, state.supplyCurrent, state.supplyFault = self._read_ina()
asm_valid = True
except Exception:
pass
if "AMD" in Device._opened_devices:
try:
state.pcieLtssm = Device["AMD"].iface.pci_dev.usb.read(0xB450, 1)[0]
except Exception:
pass
msg.valid = asm_valid and (not self.big or self.valid)
self.pm.send("egpuDockState", msg)

View File

@@ -0,0 +1,10 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
from iqpilot.selfdrive.iqmodeld.temporal_state import (
SplitTemporalState as SplitInputState,
TemporalInputState as EmacInputState,
)
__all__ = ["EmacInputState", "SplitInputState"]

View File

@@ -0,0 +1,9 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
from iqpilot._proprietary_loader import ProprietaryModuleMissing, load_private_module
try:
load_private_module(__name__, "iqpilot_private.models.emac_model_meta")
except ProprietaryModuleMissing:
from iqpilot.models_private_src.emac_model_meta import *

View File

@@ -0,0 +1,377 @@
"""
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
import subprocess
import sys
import time
from iqpilot.system.hardware import TICI
os.environ.setdefault("GMMU", "0")
if TICI:
os.environ.setdefault("DEV", "QCOM")
else:
os.environ.setdefault("DEV", "CPU")
import numpy as np
from setproctitle import setproctitle
import iqpilot.cereal.messaging as messaging
from iqpilot.cereal import car, log
from iqpilot.cereal.messaging import SubMaster
from iqpilot.cereal.services import SERVICE_LIST
from iqdbc.car.car_helpers import get_demo_car_params
from iqpilot.common.params import Params
from iqpilot.common.realtime import DT_MDL
from iqpilot.common.swaglog import cloudlog
from iqpilot.selfdrive.controls.lib.desire_helper import DesireHelper
from iqpilot.system import sentry
from iqpilot.common.steer_delay import lateral_action_delay
from iqpilot.selfdrive.iqmodeld.daemon import CalibrationAtlas, CameraIngress, FrameDropMeter
from iqpilot.selfdrive.iqmodeld.driving_action import (
DESIRE_LEN, LAT_SMOOTH_SECONDS, LONG_SMOOTH_SECONDS, get_action_from_model,
)
from iqpilot.selfdrive.iqmodeld.egpu_helpers import (
download_onnx, egpu_pkl_path, egpu_present_consented, egpu_selected, local_onnx, patch_tinygrad_fetch_fw,
quarantine_artifact, resolve_backend, usbgpu_present,
)
from iqpilot.selfdrive.iqmodeld.egpu_model import resolve_egpu_model
from iqpilot.selfdrive.iqmodeld.egpu_pipeline import EgpuPipeline, EgpuPipelineError, make_big_channel_payload
from iqpilot.selfdrive.iqmodeld.egpu_telemetry import EgpuDockTelemetry
from iqpilot.selfdrive.iqmodeld.messaging import DrivePacketMemory, populate_drive_messages, populate_odometry_message
from iqpilot.selfdrive.iqmodeld.metadata import Meta20hz
from iqpilot.selfdrive.iqmodeld.model_channel import BIG_CHANNEL, ModelChannel
from iqpilot.selfdrive.iqmodeld.model_warp import FrameWarp
from iqpilot.selfdrive.iqmodeld.parser import PhaseParser
PROCESS_NAME = "iqpilot.selfdrive.iqmodeld.iqegpumodeld"
PRESENCE_POLL_S = 5.0
COMPILE_TIMEOUT_S = 3600
LINK_UP_TIMEOUT_S = 10.0
SETUP_RETRY_BASE_S = 3.0
SETUP_RETRY_MAX_S = 30.0
def park(reason: str) -> None:
cloudlog.warning(f"iqegpumodeld parked: {reason}")
params = Params()
params.put_bool("UsbGpuFailed", True)
params.put("UsbGpuLastError", reason[:512])
while True:
time.sleep(1)
def _wait_for_egpu(params: Params) -> None:
while not usbgpu_present():
params.put_bool("UsbGpuPresent", False)
time.sleep(PRESENCE_POLL_S)
params.put_bool("UsbGpuPresent", True)
try:
from iqpilot.system.hardware.egpu_dock.flash import link_up
except Exception:
return
deadline = time.monotonic() + LINK_UP_TIMEOUT_S
while time.monotonic() < deadline:
try:
if link_up():
return
except Exception:
return
time.sleep(0.5)
def _compile_in_subprocess(meta: dict, onnx_path: str, pkl_path: str) -> None:
cmd = [sys.executable, "-m", "iqpilot.selfdrive.iqmodeld.tools.compile_egpu_model",
"--model", meta["key"], "--onnx", onnx_path, "--output", pkl_path]
compile_env = {**os.environ, "DEV": "USB+AMD:LLVM", "FLOAT16": "1",
"JIT_BATCH_SIZE": "0", "GMMU": "0"}
proc = subprocess.run(cmd, timeout=COMPILE_TIMEOUT_S, capture_output=True, text=True,
env=compile_env, preexec_fn=lambda: os.nice(20))
if proc.returncode != 0:
tail = (proc.stderr or proc.stdout or "").strip()[-800:]
raise RuntimeError(f"eGPU model compile failed (rc={proc.returncode}): {tail}")
def _ensure_artifact(params: Params, meta: dict) -> str:
pkl_path = egpu_pkl_path(meta)
if os.path.isfile(pkl_path):
return pkl_path
params.put_bool("UsbGpuCompiled", False)
onnx_path = local_onnx(meta)
if onnx_path is None:
params.put("UsbGpuSetupProgress", "0.0")
cloudlog.warning(f"iqegpumodeld downloading {meta['key']} onnx ({meta.get('download', {}).get('size', 0) / 1e6:.0f}MB)")
last = [-1.0]
def _prog(p: float) -> None:
if p - last[0] >= 0.02 or p >= 1.0:
last[0] = p
params.put("UsbGpuSetupProgress", f"{p:.3f}")
onnx_path = download_onnx(meta, progress_cb=_prog)
cloudlog.warning(f"iqegpumodeld compiling {meta['key']} for USB-AMD (one-time, can take minutes)")
_compile_in_subprocess(meta, onnx_path, pkl_path)
cloudlog.warning(f"iqegpumodeld compiled -> {pkl_path}")
return pkl_path
def _load_infer_fn(pkl_path: str, meta: dict):
patch_tinygrad_fetch_fw()
from tinygrad.tensor import Tensor
with open(pkl_path, "rb") as f:
bundle = pickle.load(f)
if bundle.get("model_sha256") != meta["sha256"]:
quarantine_artifact(pkl_path, "pkl model sha mismatch")
raise RuntimeError(f"artifact model sha {bundle.get('model_sha256')} != {meta['sha256']}")
if int(bundle.get("output_len", -1)) != int(meta["output_len"]):
quarantine_artifact(pkl_path, "pkl output_len mismatch")
raise RuntimeError(f"artifact output_len {bundle.get('output_len')} != {meta['output_len']}")
jit = bundle["run_model"]
input_dev = bundle.get("input_device", "AMD")
input_spec = bundle["input_spec"]
def infer(inputs: dict[str, np.ndarray]) -> np.ndarray:
tensors = {name: Tensor(np.ascontiguousarray(inputs[name]), device=input_dev).realize()
for name in input_spec}
out, = jit(**tensors)
return out.numpy().reshape(-1)
return infer, input_spec
def _warmup(infer_fn, input_spec: dict, output_len: int) -> float:
zeros = {name: np.zeros(shape, dtype=dtype) for name, (shape, dtype) in input_spec.items()}
t0 = time.perf_counter()
out = infer_fn(zeros)
dt = time.perf_counter() - t0
if out.shape[0] != output_len or not np.isfinite(out).all():
raise RuntimeError(f"warmup produced invalid output (len={out.shape[0]})")
return dt
def main(demo: bool = False) -> None:
cloudlog.warning("iqegpumodeld init")
sentry.set_tag("daemon", PROCESS_NAME)
cloudlog.bind(daemon=PROCESS_NAME)
setproctitle(PROCESS_NAME)
try:
os.sched_setaffinity(0, {4, 5, 6})
os.nice(-10)
except OSError as e:
cloudlog.warning(f"iqegpumodeld affinity/nice failed ({e}); continuing at defaults")
params = Params()
backend = resolve_backend(params.get_bool("IQEmacEnabled"), egpu_selected(params), egpu_present_consented(params))
if backend != "egpu":
park(f"backend resolution is {backend!r}, not egpu; refusing to own the big channel")
channel = ModelChannel(BIG_CHANNEL, create=True)
cloudlog.warning("iqegpumodeld waiting for camerad")
cameras = CameraIngress(None)
layout = cameras.layout
_wait_for_egpu(params)
params.put_bool("UsbGpuLoading", True)
attempt = 0
while True:
try:
meta = resolve_egpu_model(params)
if meta is None:
raise RuntimeError("selected big model is not in the catalog; check connectivity or pick another model")
if meta.get("split"):
params.put_bool("UsbGpuLoading", False)
park(f"model {meta['key']} needs the Mac backend; the eGPU runs fused models only")
warp = FrameWarp(cameras._primary.width, cameras._primary.height, meta["frame_skip"])
pkl_path = _ensure_artifact(params, meta)
infer_fn, input_spec = _load_infer_fn(pkl_path, meta)
warm_s = _warmup(infer_fn, input_spec, meta["output_len"])
break
except Exception as e:
attempt += 1
params.put("UsbGpuLastError", str(e)[:512])
cloudlog.warning(f"iqegpumodeld setup attempt {attempt} failed: {e}; retrying")
if not usbgpu_present():
_wait_for_egpu(params)
time.sleep(min(SETUP_RETRY_MAX_S, SETUP_RETRY_BASE_S * attempt))
params.put_bool("UsbGpuLoading", False)
params.put_bool("UsbGpuCompiled", True)
params.put("UsbGpuSetupProgress", "1.0")
cloudlog.warning(f"iqegpumodeld model: {meta['key']} ({meta['model_name']})")
cloudlog.warning(f"iqegpumodeld model up (warmup {warm_s * 1e3:.0f}ms)")
pipeline = EgpuPipeline(meta, infer_fn)
telemetry_pm = messaging.PubMaster(["egpuDockState"])
telemetry = EgpuDockTelemetry(telemetry_pm, big=True)
telemetry_every = max(1, round((1.0 / DT_MDL) / SERVICE_LIST["egpuDockState"].frequency))
sub = SubMaster(["deviceState", "carState", "roadCameraState", "extrinsicsCalibration",
"driverMonitoringState", "carControl", "lateralDelay", "iqNavState", "radarState"])
if demo:
CP = get_demo_car_params()
else:
CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams)
long_delay = CP.longitudinalActuatorDelay + LONG_SMOOTH_SECONDS
parser = PhaseParser()
memory = DrivePacketMemory()
desire_logic = DesireHelper()
frame_meter = FrameDropMeter(20.0)
warps = CalibrationAtlas()
prev_action = log.ModelDataV2.Action()
slices = {k: v for k, v in meta["output_slices"].items() if k != "pad"}
produced = 0
stats: dict[str, list[float]] = {k: [] for k in ("pull", "warp", "infer", "publish", "loop")}
iter_count = 0
skip_count = 0
last_pulled_fid = -1
last_frame_mono = time.monotonic()
t_loop = time.perf_counter()
cloudlog.warning("iqegpumodeld starting")
while True:
frame_pair = cameras.pull()
t_pull = time.perf_counter()
if frame_pair is None:
if time.monotonic() - last_frame_mono > 2.0:
cloudlog.warning("iqegpumodeld camera stream silent >2s; reconnecting VisionIPC")
cameras = CameraIngress(None)
last_frame_mono = time.monotonic()
continue
last_frame_mono = time.monotonic()
main_buf, extra_buf, main_stamp, extra_stamp = frame_pair
stats["pull"].append(t_pull - t_loop)
stats["loop"].append(time.perf_counter() - t_loop)
t_loop = time.perf_counter()
if last_pulled_fid >= 0 and main_stamp.frame_id > last_pulled_fid + 1:
skip_count += main_stamp.frame_id - last_pulled_fid - 1
last_pulled_fid = main_stamp.frame_id
iter_count += 1
if iter_count % 200 == 0:
pcts = {k: {"p50": round(sorted(v)[len(v) // 2] * 1e3, 1),
"p90": round(sorted(v)[int(len(v) * 0.9)] * 1e3, 1)}
for k, v in stats.items() if v}
cloudlog.event("iqegpu_stats", **pcts, cam_skips=skip_count, window=iter_count)
msg = " ".join(f"{k}=p50:{v['p50']:.0f}/p90:{v['p90']:.0f}ms" for k, v in pcts.items())
cloudlog.warning(f"iqegpumodeld stages: {msg} cam_skips={skip_count} over {iter_count}")
for v in stats.values():
v.clear()
skip_count = 0
sub.update(0)
v_ego = max(sub["carState"].vEgo, 0.0)
lat_delay = lateral_action_delay(params, CP, sub["lateralDelay"].lateralDelay) + LAT_SMOOTH_SECONDS
main_tfm, extra_tfm, live_calib_seen = warps.refresh(sub, layout.main_is_wide, layout.dual_camera)
dropped_frames, frame_drop_ratio, _ = frame_meter.sample(main_stamp.frame_id)
traffic = np.zeros(2, dtype=np.float32)
traffic[int(sub["driverMonitoringState"].isRHD)] = 1
desire_vec = np.zeros(DESIRE_LEN, dtype=np.float32)
if 0 <= desire_logic.desire < DESIRE_LEN:
desire_vec[desire_logic.desire] = 1
frame_delay = DT_MDL
action_delay = DT_MDL / 2
lat_action_t = lat_delay + frame_delay + action_delay
long_action_t = long_delay + frame_delay + action_delay
action_t = np.array([lat_action_t, long_action_t], dtype=np.float32)
started_at = time.perf_counter()
try:
warped = warp.run(main_buf, extra_buf, main_tfm, extra_tfm)
except Exception as e:
park(f"warp run failed: {e}")
t_warp = time.perf_counter()
stats["warp"].append(t_warp - started_at)
try:
output = pipeline.run(warped, desire_vec, traffic, action_t)
except EgpuPipelineError as e:
park(str(e))
except Exception as e:
park(f"eGPU inference failed: {e}")
t_infer = time.perf_counter()
stats["infer"].append(t_infer - t_warp)
execution_time = time.perf_counter() - started_at
sliced = {k: output[np.newaxis, sl] for k, sl in slices.items()}
outputs = parser.parse_vision_outputs(sliced)
action = get_action_from_model(outputs, prev_action, v_ego, float(lat_action_t), float(long_action_t),
lat_smooth_seconds=meta.get("lat_smooth_seconds"))
prev_action = action
model_msg = messaging.new_message("modelV2")
driving_msg = messaging.new_message("drivingModelData")
pose_msg = messaging.new_message("cameraOdometry")
iq_msg = messaging.new_message("iqDriveModelData")
populate_drive_messages(
driving_msg, model_msg, outputs, action, memory,
main_stamp.frame_id, extra_stamp.frame_id, sub["roadCameraState"].frameId,
frame_drop_ratio, main_stamp.timestamp_eof, execution_time,
live_calib_seen, Meta20hz,
)
model_msg.modelV2.big = True
desire_state = model_msg.modelV2.meta.desireState
lane_change_prob = desire_state[log.Desire.laneChangeLeft] + desire_state[log.Desire.laneChangeRight]
desire_logic.update(sub["carState"], sub["carControl"].latActive, lane_change_prob,
sub["iqNavState"], model_msg.modelV2, sub["radarState"])
model_msg.modelV2.meta.laneChangeState = desire_logic.lane_change_state
model_msg.modelV2.meta.laneChangeDirection = desire_logic.lane_change_direction
driving_msg.drivingModelData.meta.laneChangeState = desire_logic.lane_change_state
driving_msg.drivingModelData.meta.laneChangeDirection = desire_logic.lane_change_direction
iq_msg.iqDriveModelData.turnSignalDirection = desire_logic.lane_turn_direction
populate_odometry_message(pose_msg, outputs, main_stamp.frame_id, dropped_frames,
main_stamp.timestamp_eof, live_calib_seen)
channel.write(main_stamp.frame_id, make_big_channel_payload(
main_stamp.frame_id, live_calib_seen, execution_time, (t_infer - t_warp) * 1e3, {
"modelV2": model_msg.to_bytes(),
"drivingModelData": driving_msg.to_bytes(),
"cameraOdometry": pose_msg.to_bytes(),
"iqDriveModelData": iq_msg.to_bytes(),
}))
stats["publish"].append(time.perf_counter() - t_infer)
produced += 1
if produced == 1 or produced % 100 == 0:
infer_ms = (t_infer - t_warp) * 1e3
cloudlog.warning(f"iqegpumodeld producing: frame={main_stamp.frame_id} total={execution_time * 1e3:.0f}ms infer={infer_ms:.0f}ms count={produced}")
if produced % telemetry_every == 0:
telemetry.send()
frame_meter.commit(main_stamp.frame_id)
if __name__ == "__main__":
try:
import argparse
ap = argparse.ArgumentParser()
ap.add_argument("--demo", action="store_true")
args = ap.parse_args()
main(demo=args.demo)
except KeyboardInterrupt:
cloudlog.warning("iqegpumodeld got SIGINT")
except Exception:
import traceback
sentry.capture_exception()
cloudlog.exception("iqegpumodeld crashed, parking")
park(f"crashed: {traceback.format_exc(limit=8)}")

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 iqpilot.cereal import log
from iqpilot.selfdrive.iqmodeld.models.helpers import plan_x_idxs_helper
from iqpilot.selfdrive.iqmodeld.config import ModelConstants, Plan
from iqpilot.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 fill_xyz_poly(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 fill_lane_line_meta(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)
fill_xyz_poly(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()
fill_lane_line_meta(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 iqpilot.cereal import custom
from tinygrad.nn.onnx import OnnxPBParser
from iqpilot.selfdrive.iqmodeld.models.helpers import get_active_bundle
from 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,58 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
from __future__ import annotations
import mmap
import os
import pickle
import struct
from iqpilot.common.swaglog import cloudlog
SMALL_CHANNEL = "/dev/shm/iqpilot_smallmodel"
BIG_CHANNEL = "/dev/shm/iqpilot_bigmodel"
SHM_SIZE = 8 * 1024 * 1024
HEADER = struct.Struct("<QqQ")
class ModelChannel:
def __init__(self, path: str, create: bool):
if create:
fd = os.open(path, os.O_CREAT | os.O_RDWR, 0o600)
os.ftruncate(fd, SHM_SIZE)
else:
fd = os.open(path, os.O_RDWR)
self.mm = mmap.mmap(fd, SHM_SIZE)
os.close(fd)
if create:
self.mm[:HEADER.size] = HEADER.pack(0, -1, 0)
def write(self, frame_id: int, payload: dict) -> None:
data = pickle.dumps(payload, protocol=pickle.HIGHEST_PROTOCOL)
if HEADER.size + len(data) > SHM_SIZE:
cloudlog.error(f"model payload {len(data)} bytes exceeds shm {SHM_SIZE}, dropping frame {frame_id}")
return
seq = HEADER.unpack(self.mm[:HEADER.size])[0]
HEADER.pack_into(self.mm, 0, seq + 1, frame_id, len(data))
self.mm[HEADER.size:HEADER.size + len(data)] = data
HEADER.pack_into(self.mm, 0, seq + 2, frame_id, len(data))
def peek_frame_id(self) -> int | None:
seq, frame_id, length = HEADER.unpack(self.mm[:HEADER.size])
if seq == 0 or seq % 2 != 0 or length == 0:
return None
return frame_id
def read(self) -> tuple[int, dict] | None:
seq1, frame_id, length = HEADER.unpack(self.mm[:HEADER.size])
if seq1 == 0 or seq1 % 2 != 0 or length == 0:
return None
data = bytes(self.mm[HEADER.size:HEADER.size + length])
seq2 = HEADER.unpack(self.mm[:HEADER.size])[0]
if seq1 != seq2:
return None
try:
return frame_id, pickle.loads(data)
except Exception:
return None

View File

@@ -0,0 +1,80 @@
"""
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
import numpy as np
from iqpilot.common.swaglog import cloudlog
from iqpilot.system.hardware.hw import Paths
def _load_bundle(pkl_path: str, cam_w: int, cam_h: int, frame_skip: int) -> dict:
with open(pkl_path, "rb") as f:
bundle = pickle.load(f)
if bundle.get("frame_skip") != frame_skip:
raise RuntimeError(f"frame_skip {bundle.get('frame_skip')} != {frame_skip}")
if (cam_w, cam_h) not in bundle:
raise RuntimeError(f"missing {cam_w}x{cam_h}; has {[k for k in bundle if isinstance(k, tuple)]}")
_verify_selftest(bundle, cam_w, cam_h)
return bundle
def _verify_selftest(bundle: dict, cam_w: int, cam_h: int) -> None:
want = bundle.get("selftest")
if not want:
raise RuntimeError("warp artifact predates the self-test; recompiling")
from iqpilot.system.camerad.cameras.nv12_info import get_nv12_info
from iqpilot.selfdrive.iqmodeld.tools.compile_warp import selftest_digest
nv12_size = get_nv12_info(cam_w, cam_h)[3]
got = selftest_digest(bundle[(cam_w, cam_h)], cam_w, cam_h, nv12_size)
if got != want:
raise RuntimeError(f"warp self-test {got[:12]} != {want[:12]}; artifact computes differently here")
class FrameWarp:
def __init__(self, cam_w: int, cam_h: int, frame_skip: int):
from tinygrad.tensor import Tensor
pkl_path = os.path.join(Paths.model_root(), f"emac_warp_{cam_w}x{cam_h}_tinygrad.pkl")
bundle = None
if os.path.isfile(pkl_path):
try:
bundle = _load_bundle(pkl_path, cam_w, cam_h, frame_skip)
except Exception as e:
cloudlog.warning(f"warp artifact unusable ({e}); discarding and recompiling")
os.remove(pkl_path)
if bundle is None:
cloudlog.warning(f"warp artifact missing; compiling for {cam_w}x{cam_h} (one-time)")
from iqpilot.selfdrive.iqmodeld.tools.compile_warp import compile_warp
compile_warp(cam_w, cam_h, pkl_path, frame_skip=frame_skip)
cloudlog.warning(f"warp compiled -> {pkl_path}")
bundle = _load_bundle(pkl_path, cam_w, cam_h, frame_skip)
self._jit = bundle[(cam_w, cam_h)]
self._npy = {"tfm": np.zeros((3, 3), dtype=np.float32), "big_tfm": np.zeros((3, 3), dtype=np.float32)}
self._tensors = {k: Tensor(v, device="NPY").realize() for k, v in self._npy.items()}
self._blob_cache: dict[tuple[str, int], object] = {}
self._Tensor = Tensor
def _frame_tensor(self, key: str, buf):
from tinygrad.device import Device
arr = np.frombuffer(buf.data, dtype=np.uint8)
ck = (key, arr.ctypes.data)
t = self._blob_cache.get(ck)
if t is None:
t = self._Tensor.from_blob(arr.ctypes.data, (arr.size,), dtype="uint8", device=Device.DEFAULT)
self._blob_cache[ck] = t
return t
def run(self, main_buf, extra_buf, main_tfm: np.ndarray, extra_tfm: np.ndarray) -> np.ndarray:
self._npy["tfm"][:] = main_tfm
self._npy["big_tfm"][:] = extra_tfm
warped = self._jit(tfm=self._tensors["tfm"], big_tfm=self._tensors["big_tfm"],
frame=self._frame_tensor("img", main_buf),
big_frame=self._frame_tensor("big_img", extra_buf))
return warped.numpy().astype(np.uint8, copy=False)

View File

@@ -0,0 +1,373 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
from __future__ import annotations
import json
import os
import threading
import time
from collections import deque
from iqpilot.cereal.messaging import PubMaster, log_from_bytes
from setproctitle import setproctitle
from iqpilot.common.filter_simple import FirstOrderFilter
from iqpilot.common.params import Params
from iqpilot.common.realtime import config_realtime_process
from iqpilot.common.swaglog import cloudlog
from iqpilot.selfdrive.iqmodeld.model_channel import BIG_CHANNEL, SMALL_CHANNEL, ModelChannel
PROCESS_NAME = "iqpilot.selfdrive.iqmodeld.modeld_selector"
BIG_MODEL_DEADLINE = float(os.getenv("IQEMAC_BIG_DEADLINE_MS", "45")) / 1000.0
BIG_MAX_LAG_FRAMES = int(os.getenv("IQEMAC_MAX_BIG_LAG_FRAMES", "6"))
BIG_FUTURE_ACCEPT = int(os.getenv("IQEMAC_BIG_FUTURE_ACCEPT", "2"))
BIG_ANCHOR_MS = float(os.getenv("IQEMAC_BIG_ANCHOR_MS", "90"))
BIG_WAIT_FLOOR_S = 0.002
BIG_WAIT_CEIL_S = float(os.getenv("IQEMAC_BIG_WAIT_CEIL_MS", "58")) / 1000.0
BIG_MISS_LIMIT = int(os.getenv("IQEMAC_BIG_MISS_LIMIT", "80"))
ACTIVATE_WINDOW = int(os.getenv("IQEMAC_ACTIVATE_WINDOW", "50"))
ACTIVATE_FRAC = float(os.getenv("IQEMAC_ACTIVATE_FRAC", "0.7"))
REARM_LIMIT = int(os.getenv("IQEMAC_REARM_LIMIT", "2"))
MODEL_FREQ = 20.0
WARMUP_FRAMES = 40
STATUS_WINDOW = int(os.getenv("IQEMAC_STATUS_EVERY", "20"))
SELECTOR_SERVICES = ["modelV2", "drivingModelData", "cameraOdometry", "iqDriveModelData"]
EMAC_STATUS_KEYS = {
"active": "MacModelActive", "failed": "MacModelFailed", "last_error": "MacModelLastError",
"latency_ms": "MacModelLatencyMs", "status": "MacModelStatus",
"reachable": "MacModelReachable", "progress": "MacModelDownloadProgress",
}
EGPU_STATUS_KEYS = {
"active": "UsbGpuActive", "failed": "UsbGpuFailed", "last_error": "UsbGpuLastError",
"latency_ms": "UsbGpuLatencyMs", "status": "UsbGpuStatus",
"reachable": "UsbGpuPresent", "progress": "UsbGpuSetupProgress",
}
def backend_status_keys(emac_enabled: bool, egpu_enabled: bool, egpu_present: bool = False) -> dict[str, str]:
from iqpilot.selfdrive.iqmodeld.egpu_helpers import resolve_backend
return EGPU_STATUS_KEYS if resolve_backend(emac_enabled, egpu_enabled, egpu_present) == "egpu" else EMAC_STATUS_KEYS
def resolve_status_keys(params):
from iqpilot.selfdrive.iqmodeld.egpu_helpers import egpu_present_consented, egpu_selected
return backend_status_keys(params.get_bool("IQEmacEnabled"), egpu_selected(params), egpu_present_consented(params))
def resolve_model_name(params, keys) -> str:
if keys is EGPU_STATUS_KEYS:
from iqpilot.selfdrive.iqmodeld.egpu_model import DEFAULT_EGPU_MODEL, resolve_egpu_model
resolved = resolve_egpu_model(params, allow_refresh=False)
return resolved["key"] if resolved else DEFAULT_EGPU_MODEL
name = params.get("IQEmacModel") or b"lebrowski"
return name.decode() if isinstance(name, bytes) else name
class AsyncParamWriter:
def __init__(self, params: Params):
self._params = params
self._pending: dict[str, object] = {}
self._lock = threading.Lock()
self._event = threading.Event()
threading.Thread(target=self._drain, daemon=True).start()
def put(self, key: str, value) -> None:
with self._lock:
self._pending[key] = value
self._event.set()
def put_bool(self, key: str, value: bool) -> None:
self.put(key, bool(value))
def _drain(self) -> None:
while True:
self._event.wait()
self._event.clear()
with self._lock:
batch, self._pending = self._pending, {}
for key, value in batch.items():
try:
if isinstance(value, bool):
self._params.put_bool(key, value)
else:
self._params.put(key, value)
except Exception:
cloudlog.exception(f"async param write failed: {key}")
def wait_for_big(big_channel, target: int, deadline: float, min_frame: int = -1,
max_lag_frames: int = BIG_MAX_LAG_FRAMES) -> tuple[dict | None, int | None]:
big_peek = None
grab_at = deadline - 0.004
while time.perf_counter() < deadline:
bfid = big_channel.peek_frame_id()
big_peek = bfid
if bfid == target - 1 and time.perf_counter() < grab_at:
time.sleep(0.0005)
continue
if bfid is not None and min_frame < bfid <= target + BIG_FUTURE_ACCEPT and target - bfid <= max_lag_frames:
got = big_channel.read()
if got is not None and got[0] == bfid:
return got[1], big_peek
break
if bfid is None or bfid <= min_frame or bfid > target + BIG_FUTURE_ACCEPT or target - bfid > max_lag_frames:
break
time.sleep(0.0005)
return None, big_peek
class BigLatch:
def __init__(self, miss_limit: int = BIG_MISS_LIMIT, activate_window: int = ACTIVATE_WINDOW,
activate_frac: float = ACTIVATE_FRAC, rearm_limit: int = REARM_LIMIT):
self.miss_limit = miss_limit
self.activate_window = activate_window
self.activate_need = int(round(activate_window * activate_frac))
self.rearm_limit = rearm_limit
self.active = False
self.done = False
self._miss = 0
self._window: deque[bool] = deque(maxlen=activate_window)
self._retires = 0
def update(self, used_big: bool) -> tuple[bool, bool]:
if self.done:
return False, False
if not self.active:
self._window.append(used_big)
if len(self._window) >= self.activate_window and sum(self._window) >= self.activate_need:
self.active = True
self._miss = 0
self._window.clear()
return True, False
if used_big:
self._miss = 0
elif self.active:
self._miss += 1
if self._miss >= self.miss_limit:
self.active = False
self._miss = 0
self._window.clear()
self._retires += 1
self.done = self._retires > self.rearm_limit
return False, True
return False, False
def _patch_and_send(pm: PubMaster, payload: dict, frame_drop_perc: float, selector_dropped: int,
target: int, source_lag: int, mismatch: bool | None = None) -> None:
msgs = payload["msgs"]
if mismatch is None:
mismatch = source_lag > 0
model_msg = log_from_bytes(msgs["modelV2"]).as_builder()
if mismatch:
model_msg.modelV2.frameId = target
model_msg.modelV2.frameAge = max(model_msg.modelV2.frameAge, source_lag)
model_msg.modelV2.frameDropPerc = frame_drop_perc
pm.send("modelV2", model_msg)
driving_msg = log_from_bytes(msgs["drivingModelData"]).as_builder()
if mismatch:
driving_msg.drivingModelData.frameId = target
driving_msg.drivingModelData.frameDropPerc = frame_drop_perc
pm.send("drivingModelData", driving_msg)
pose_msg = log_from_bytes(msgs["cameraOdometry"]).as_builder()
if mismatch:
pose_msg.cameraOdometry.frameId = target
pose_msg.valid = bool(payload["live_calib_seen"]) and selector_dropped < 1 and not mismatch
pm.send("cameraOdometry", pose_msg)
pm.send("iqDriveModelData", msgs["iqDriveModelData"])
def _read_float(params, key: str, default: float) -> float:
v = params.get(key)
try:
return float(v) if v is not None else default
except (TypeError, ValueError):
return default
def main() -> None:
cloudlog.warning("modeld_selector init")
cloudlog.bind(daemon=PROCESS_NAME)
setproctitle(PROCESS_NAME)
config_realtime_process([0, 1, 2, 3], 54)
params = Params()
keys = resolve_status_keys(params)
pwriter = AsyncParamWriter(params)
pwriter.put_bool(keys["active"], False)
pwriter.put_bool(keys["failed"], False)
pm = PubMaster(SELECTOR_SERVICES)
small_channel: ModelChannel | None = None
big_channel: ModelChannel | None = None
latch = BigLatch()
big_used_count = 0
run_count = 0
last_published = -1
last_big_published = -1
frame_dropped_filter = FirstOrderFilter(0.0, 10.0, 1.0 / MODEL_FREQ)
recent_big = deque(maxlen=STATUS_WINDOW)
model_name = resolve_model_name(params, keys)
last_backend_check = 0.0
last_latency_ms = 0.0
last_source_lag = 0
miss_reasons = {"no_head": 0, "already_used": 0, "far_future": 0,
"too_stale": 0, "head_prev_timeout": 0, "read_race": 0}
cloudlog.warning(f"modeld_selector starting (max_big_lag_frames={BIG_MAX_LAG_FRAMES})")
while True:
if small_channel is None:
try:
small_channel = ModelChannel(SMALL_CHANNEL, create=False)
except OSError:
time.sleep(0.05)
continue
if big_channel is None:
try:
big_channel = ModelChannel(BIG_CHANNEL, create=False)
except OSError:
big_channel = None
fid = small_channel.peek_frame_id()
if fid is None or fid == last_published:
time.sleep(0.0005)
continue
if last_published >= 0 and fid < last_published - 1:
cloudlog.warning(f"modeld_selector frame reset {last_published} -> {fid}; re-arming")
last_published = -1
last_big_published = -1
big_used_count = 0
run_count = 0
latch = BigLatch()
pwriter.put_bool(keys["active"], False)
pwriter.put_bool(keys["failed"], False)
now_mono = time.monotonic()
if now_mono - last_backend_check > 1.0:
last_backend_check = now_mono
new_keys = resolve_status_keys(params)
if new_keys is not keys:
cloudlog.warning(f"modeld_selector backend changed {keys['active']} -> {new_keys['active']}; re-arming")
pwriter.put_bool(keys["active"], False)
pwriter.put_bool(keys["failed"], False)
keys = new_keys
model_name = resolve_model_name(params, keys)
recent_big.clear()
last_big_published = -1
big_used_count = 0
run_count = 0
latch = BigLatch()
pwriter.put_bool(keys["active"], False)
pwriter.put_bool(keys["failed"], False)
target = fid
t_start = time.perf_counter()
small_payload = None
got = small_channel.read()
if got is not None and got[0] == target:
small_payload = got[1]
payload = None
used_big = False
big_peek = None
if big_channel is not None and not latch.done:
deadline = t_start + BIG_MODEL_DEADLINE
sof_ns = (small_payload or {}).get("timestamp_sof")
if sof_ns:
remaining = (BIG_ANCHOR_MS / 1000.0) - (time.clock_gettime(time.CLOCK_BOOTTIME) - sof_ns / 1e9)
deadline = t_start + min(max(remaining, BIG_WAIT_FLOOR_S), BIG_WAIT_CEIL_S)
payload, big_peek = wait_for_big(big_channel, target, deadline,
last_big_published, BIG_MAX_LAG_FRAMES)
used_big = payload is not None
if not used_big:
if big_peek is None:
miss_reasons["no_head"] += 1
elif big_peek <= last_big_published:
miss_reasons["already_used"] += 1
elif big_peek > target + BIG_FUTURE_ACCEPT:
miss_reasons["far_future"] += 1
elif target - big_peek > BIG_MAX_LAG_FRAMES:
miss_reasons["too_stale"] += 1
elif big_peek == target - 1:
miss_reasons["head_prev_timeout"] += 1
else:
miss_reasons["read_race"] += 1
if payload is None:
payload = small_payload
if payload is None:
got = small_channel.read()
if got is not None and got[0] == target:
payload = got[1]
activated_now, failed_now = latch.update(used_big)
if activated_now:
pwriter.put_bool(keys["active"], True)
pwriter.put_bool(keys["failed"], False)
cloudlog.warning(f"modeld_selector switched to BIG model at frame {target}")
elif failed_now:
pwriter.put_bool(keys["active"], False)
pwriter.put_bool(keys["failed"], latch.done)
pwriter.put(keys["last_error"], "big model stalled onroad; local fallback latched"
if latch.done else "big model stalled onroad; small active, big may re-arm")
if latch.done:
cloudlog.warning(f"modeld_selector big stalled, staying on small until next ignition (frame {target})")
else:
cloudlog.warning(f"modeld_selector big stalled, small active; big may re-arm after a clean streak (frame {target})")
if payload is not None:
selector_dropped = max(0, target - last_published - 1) if last_published >= 0 else 0
frames_dropped = frame_dropped_filter.update(min(selector_dropped, 10))
if run_count < WARMUP_FRAMES:
frame_dropped_filter.x = 0.0
frames_dropped = 0.0
run_count += 1
recent_big.append(used_big)
if used_big:
big_used_count += 1
big_fid = int(payload.get("frame_id", big_peek if big_peek is not None else target))
last_big_published = min(big_fid, target)
last_latency_ms = float(payload.get("model_execution_time", 0.0)) * 1e3
source_lag = max(0, target - int(payload.get("frame_id", target)))
frame_mismatch = int(payload.get("frame_id", target)) != target
last_source_lag = source_lag
if run_count % STATUS_WINDOW == 0:
hit_rate = (sum(recent_big) / len(recent_big)) if recent_big else 0.0
pwriter.put(keys["latency_ms"], last_latency_ms)
pwriter.put(keys["status"], json.dumps({
"active": latch.active,
"failed": latch.done,
"hit_rate": round(hit_rate, 3),
"latency_ms": round(last_latency_ms, 1),
"source_lag_frames": last_source_lag,
"model": model_name,
"reachable": params.get_bool(keys["reachable"]),
"download_progress": _read_float(params, keys["progress"], 1.0),
"ts_mono": round(time.monotonic(), 1),
}))
if run_count % 100 == 0:
cloudlog.warning(f"modeld_selector misses: {miss_reasons}")
cloudlog.warning(f"modeld_selector: big_used={big_used_count}/{run_count} "
f"last_big_peek={big_peek} target={target} active={latch.active} "
f"max_big_lag={BIG_MAX_LAG_FRAMES}")
frame_drop_perc = 100.0 * frames_dropped / (1.0 + frames_dropped)
_patch_and_send(pm, payload, frame_drop_perc, selector_dropped, target, source_lag, frame_mismatch)
last_published = target
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
cloudlog.warning("modeld_selector got SIGINT")

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/
"""
from __future__ import annotations
import os
import re
from pathlib import Path
from iqpilot.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,11 @@
#!/usr/bin/env python3
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
from 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,313 @@
#!/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 iqpilot.cereal import custom
from iqpilot.common.params import Params
from iqpilot.common.swaglog import cloudlog
from iqpilot._proprietary_loader import ProprietaryModuleMissing, load_private_module
from iqpilot.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_INDEX_KEY = "ModelManager_PendingIndex"
_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):
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 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.remove(_PENDING_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):
return
queued_download = params.get(_DOWNLOAD_INDEX_KEY)
try:
select_default_model(params)
if queued_download is not None:
params.put(_DOWNLOAD_INDEX_KEY, queued_download)
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,9 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
"""
from iqpilot.common.steer_delay import cached_steer_delay
class InferenceStateBase:
def __init__(self):
self.lat_delay = cached_steer_delay()

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,231 @@
"""
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 iqpilot.cereal import custom
from iqpilot.common.swaglog import cloudlog
from iqpilot.system.hardware import TICI
from iqpilot.system.hardware.hw import Paths as _hw_paths
from iqpilot.selfdrive.iqmodeld.models.helpers import get_active_bundle as _fetch_bundle
from 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 iqpilot.selfdrive.iqmodeld.native.iqmodel_pyx import GpuMemorySlot, RoadProjector
else:
def _resolve_native_types() -> tuple[Any, Any]:
try:
from iqpilot.selfdrive.iqmodeld.native.iqmodel_pyx import GpuMemorySlot as iq_clmem
from 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()
# real metadata pkls are a few KB; anything bigger is a model artifact wrongly
# referenced as metadata (pre-fix manifests self-referenced the artifact), and
# unpickling it here double-loads the model onto the GPU
_META_MAX_BYTES = 1 << 20
def load_artifact_metadata(metadata_filename):
"""Read one artifact's metadata pkl: (input shapes, output slices)."""
try:
path = os.path.join(CUSTOM_MODEL_PATH, metadata_filename)
if os.path.getsize(path) > _META_MAX_BYTES:
cloudlog.error(f"metadata pkl {metadata_filename} is artifact-sized, refusing to unpickle it")
return tuple({} for _ in _META_FIELDS)
with open(path, 'rb') as fh:
blob = _pk.load(fh)
return tuple(blob.get(field, {}) for field in _META_FIELDS)
except Exception:
cloudlog.exception(f"unreadable metadata pkl {metadata_filename}, continuing without it")
return tuple({} for _ 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 _qcom_models(active)}
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 _qcom_models(bundle) -> list:
# usbeMac artifacts ride along in a bundle for the eGPU host; they are never
# loaded on QCOM and must not affect runner classification
return [m for m in bundle.models if m.type.raw != ModelType.usbeMac]
def _single_artifact_prefix(bundle, prefix: str) -> bool:
models = _qcom_models(bundle)
return len(models) == 1 and 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 _qcom_models(bundle)}
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 iqpilot.selfdrive.iqmodeld.models.runners.tinygrad.tinygrad_runner import (TinygradRunner,
TinygradSplitRunner)
bundle = _fetch_bundle()
# an eMac-only bundle (no QCOM-loadable models) runs the stock default on
# device; the big host serves the bundle's precompiled artifact
if not (bundle and bundle.models and _qcom_models(bundle)):
return TinygradRunner(ModelType.supercombo)
if _is_supercombo_bundle(bundle):
from iqpilot.selfdrive.iqmodeld.models.runners.tinygrad.supercombo_runner import TinygradSupercomboRunner
return TinygradSupercomboRunner()
if _is_fused_bundle(bundle):
from iqpilot.selfdrive.iqmodeld.models.runners.tinygrad.fused_runner import TinygradFusedRunner
return TinygradFusedRunner()
if _is_split_bundle(bundle) and has_combined_split_artifact(bundle):
from iqpilot.selfdrive.iqmodeld.models.runners.tinygrad.combined_split_runner import TinygradCombinedSplitRunner
return TinygradCombinedSplitRunner()
if _is_split_bundle(bundle):
return TinygradSplitRunner()
return TinygradRunner(_qcom_models(bundle)[0].type.raw)

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,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 iqpilot.selfdrive.iqmodeld.models.combined_artifact import resolve_combined_split_artifact
from iqpilot.selfdrive.iqmodeld.models.helpers import get_active_bundle
from iqpilot.selfdrive.iqmodeld.models.runners.model_runner import NumpyDict, ShapeDict, SliceDict
from iqpilot.selfdrive.iqmodeld.models.runners.model_runner import ModelRunner
from iqpilot.selfdrive.iqmodeld.models.split_model_constants import SplitModelConstants
from 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,178 @@
"""
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 iqpilot.selfdrive.iqmodeld.models.runners.model_runner import (
CUSTOM_MODEL_PATH, NumpyDict, ShapeDict, SliceDict,
)
from iqpilot.selfdrive.iqmodeld.models.runners.model_runner import ModelRunner
from iqpilot.selfdrive.iqmodeld.models.split_model_constants import SplitModelConstants
from 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):
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])),
}
on_shapes = self._on_meta['input_shapes']
captured = self._run_policy.captured
jit_shapes = {
name: tuple(int(s) for s in view.shape)
for name, (view, _vars, _dtype, _device) in zip(captured.expected_names, captured.expected_input_info)
}
def policy_input_shape(name):
shape = on_shapes.get(name, jit_shapes.get(name))
if shape is None:
raise ValueError(f"fused pkl declares no shape for policy input {name}")
return shape
self._npy_buffers = {
'desire': np.zeros(dp[2], dtype=np.float32),
'traffic_convention': np.zeros(policy_input_shape('traffic_convention'), dtype=np.float32),
'tfm': np.zeros((3, 3), dtype=np.float32),
'big_tfm': np.zeros((3, 3), dtype=np.float32),
}
if 'action_t' in jit_shapes:
self._npy_buffers['action_t'] = np.zeros(policy_input_shape('action_t'), 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:
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 and 'action_t' in self._npy_buffers:
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')
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)
policy_inputs = dict(
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'))
if 'action_t' in self._npy_buffers:
policy_inputs['action_t'] = npy('action_t')
vision_out_t, on_out_t, off_out_t = self._run_policy(**policy_inputs)
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 iqpilot.selfdrive.iqmodeld.models.runners.model_runner import ModelType, NumpyDict
from iqpilot.selfdrive.iqmodeld.models.runners.model_runner import RunnerRoot
from 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,339 @@
"""
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 iqpilot.common.params import Params
from iqpilot.selfdrive.iqmodeld.models.runners.model_runner import CUSTOM_MODEL_PATH, NumpyDict, ShapeDict, SliceDict
from iqpilot.selfdrive.iqmodeld.models.runners.model_runner import ModelRunner
from iqpilot.selfdrive.iqmodeld.models.split_model_constants import SplitModelConstants
from 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):
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()
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']
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()
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)
def _run_model(self) -> NumpyDict:
raise RuntimeError("supercombo path goes through run_fused(), not _run_model()")

View File

@@ -0,0 +1,190 @@
"""
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.dtype import dtypes
from tinygrad.tensor import Tensor
from iqpilot.selfdrive.iqmodeld.models.runners.model_runner import (
CLMemDict,
CUSTOM_MODEL_PATH,
FrameDict,
ModelType,
NumpyDict,
ShapeDict,
SliceDict,
)
from iqpilot.selfdrive.iqmodeld.models.runners.model_runner import ModelRunner
from iqpilot.selfdrive.iqmodeld.models.runners.tinygrad.model_types import (
OffPolicyTinygrad,
OnPolicyTinygrad,
PolicyTinygrad,
SupercomboTinygrad,
VisionTinygrad,
)
from iqpilot.selfdrive.iqmodeld.models.split_model_constants import SplitModelConstants
from iqpilot.selfdrive.iqmodeld.config import ModelConstants
from iqpilot.selfdrive.iqmodeld.runtime.tinygrad import qcom_tensor_from_opencl_address
from iqpilot.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)
for name, spec in self._input_plan.items():
if "img" in name and spec.dtype is not dtypes.uint8:
raise ValueError(f"{asset_name}: image input {name} expects {spec.dtype}, incompatible with uint8 warp buffer")
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,89 @@
import numpy as np
def index_function(idx, max_val=192, max_idx=32):
return max_val * ((idx/max_idx)**2)
class SplitModelConstants:
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.]
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
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
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)
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)

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 iqpilot.selfdrive.iqmodeld.models.split_model_constants import SplitModelConstants
from iqpilot.selfdrive.iqmodeld.config import ModelConstants
def safe_exp(values, out=None):
return np.exp(np.clip(values, -np.inf, 11), out=out)
def sigmoid(values):
return 1.0 / (1.0 + safe_exp(-values))
def _softmax_last(values, axis=-1):
values -= np.max(values, axis=axis, keepdims=True)
if values.dtype in (np.float32, np.float64):
safe_exp(values, out=values)
else:
values = safe_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 = safe_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,131 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
from __future__ import annotations
import math
import numpy as np
DEFAULT_FRAME_SKIP = 4
MODEL_INPUT_SPEC: dict[str, tuple[tuple[int, ...], str]] = {
"img": ((1, 12, 128, 256), "uint8"),
"big_img": ((1, 12, 128, 256), "uint8"),
"desire_pulse": ((1, 25, 8), "float32"),
"traffic_convention": ((1, 2), "float32"),
"features_buffer": ((1, 24, 512), "float32"),
"action_t": ((1, 2), "float32"),
}
def spec_from_meta(meta: dict) -> dict[str, tuple[tuple[int, ...], str]] | None:
shapes = meta.get("input_shapes")
if not shapes:
return None
return {name: (tuple(shape), "uint8" if name in ("img", "big_img") else "float32")
for name, shape in shapes.items()}
class TemporalInputState:
def __init__(self, frame_skip: int, spec: dict[str, tuple[tuple[int, ...], str]] = MODEL_INPUT_SPEC):
self.frame_skip = frame_skip
img = spec["img"][0]
fb = spec["features_buffer"][0]
dp = spec["desire_pulse"][0]
self.n_frames = img[1] // 6
img_q_shape = (frame_skip * (self.n_frames - 1) + 1, 6, img[2], img[3])
self._img_shape = img
self._fb_shape = fb
self._dp_shape = dp
feat_dim = math.prod(fb[2:])
self.img_q = np.zeros(img_q_shape, dtype=np.uint8)
self.big_img_q = np.zeros(img_q_shape, dtype=np.uint8)
self.feat_q = np.zeros((frame_skip * fb[1], fb[0], feat_dim), dtype=np.float32)
self.desire_q = np.zeros((frame_skip * dp[1], dp[0], dp[2]), dtype=np.float32)
self.prev_desire = np.zeros(dp[2], dtype=np.float32)
self.prev_feat = np.zeros((fb[0], feat_dim), dtype=np.float32)
@staticmethod
def _shift_append(q: np.ndarray, new_val: np.ndarray) -> None:
q[:-1] = q[1:]
q[-1] = new_val
def push_and_materialize(self, warped: np.ndarray, desire_pulse: np.ndarray,
traffic_convention: np.ndarray, action_t: np.ndarray,
) -> dict[str, np.ndarray]:
fs = self.frame_skip
cur = desire_pulse.astype(np.float32).copy()
cur[0] = 0
pulse = np.where(cur - self.prev_desire > 0.99, cur, 0).astype(np.float32)
self.prev_desire[:] = cur
self._shift_append(self.img_q, warped[0])
self._shift_append(self.big_img_q, warped[1])
self._shift_append(self.desire_q, pulse.reshape(self._dp_shape[0], self._dp_shape[2]))
self._shift_append(self.feat_q, self.prev_feat)
dp = self._dp_shape
return {
"img": np.ascontiguousarray(self.img_q[::fs]).reshape(self._img_shape),
"big_img": np.ascontiguousarray(self.big_img_q[::fs]).reshape(self._img_shape),
"features_buffer": np.ascontiguousarray(self.feat_q[::fs]).reshape(self._fb_shape),
"desire_pulse": self.desire_q.reshape(dp[1], fs, dp[0], dp[2]).max(axis=1).reshape(dp),
"traffic_convention": traffic_convention.astype(np.float32).reshape(1, -1),
"action_t": action_t.astype(np.float32).reshape(1, -1),
}
def note_hidden_state(self, model_output: np.ndarray, hidden_slice: slice) -> None:
self.prev_feat[:] = model_output[hidden_slice].reshape(self.prev_feat.shape)
class SplitTemporalState:
def __init__(self, frame_skip: int, img_shape: tuple[int, ...],
feature_shape: tuple[int, ...], desire_shape: tuple[int, ...]):
self.frame_skip = frame_skip
self._img_shape = tuple(img_shape)
self._fb_shape = tuple(feature_shape)
self._dp_shape = tuple(desire_shape)
n_frames = img_shape[1] // 6
img_q_shape = (frame_skip * (n_frames - 1) + 1, 6, img_shape[2], img_shape[3])
self.img_q = np.zeros(img_q_shape, dtype=np.uint8)
self.big_img_q = np.zeros(img_q_shape, dtype=np.uint8)
self.feat_q = np.zeros((frame_skip * (feature_shape[1] - 1) + 1, feature_shape[0], feature_shape[2]),
dtype=np.float32)
self.desire_q = np.zeros((frame_skip * desire_shape[1], desire_shape[0], desire_shape[2]), dtype=np.float32)
self.prev_desire = np.zeros(desire_shape[2], dtype=np.float32)
def materialize_vision(self, warped: np.ndarray, desire: np.ndarray) -> dict[str, np.ndarray]:
fs = self.frame_skip
cur = desire.astype(np.float32).copy()
cur[0] = 0
pulse = np.where(cur - self.prev_desire > 0.99, cur, 0).astype(np.float32)
self.prev_desire[:] = cur
TemporalInputState._shift_append(self.img_q, warped[0])
TemporalInputState._shift_append(self.big_img_q, warped[1])
TemporalInputState._shift_append(self.desire_q, pulse.reshape(self._dp_shape[0], self._dp_shape[2]))
return {
"img": np.ascontiguousarray(self.img_q[::fs]).reshape(self._img_shape),
"big_img": np.ascontiguousarray(self.big_img_q[::fs]).reshape(self._img_shape),
}
def materialize_policy(self, vision_feature: np.ndarray, traffic_convention: np.ndarray,
action_t: np.ndarray | None = None) -> dict[str, np.ndarray]:
fs = self.frame_skip
TemporalInputState._shift_append(self.feat_q, vision_feature.reshape(self._fb_shape[0], self._fb_shape[2]))
dp = self._dp_shape
out = {
"features_buffer": np.ascontiguousarray(self.feat_q[::fs]).reshape(self._fb_shape),
"desire_pulse": self.desire_q.reshape(dp[1], fs, dp[0], dp[2]).max(axis=1).reshape(dp),
"traffic_convention": traffic_convention.astype(np.float32).reshape(1, -1),
}
if action_t is not None:
out["action_t"] = action_t.astype(np.float32).reshape(1, -1)
return out

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,95 @@
// Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
#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,81 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
from __future__ import annotations
from types import SimpleNamespace
import numpy as np
import pytest
from iqpilot.cereal import log
from iqpilot.selfdrive.iqmodeld.config import Plan
from iqpilot.selfdrive.iqmodeld.daemon import NeuralEngineState, _merged_plan
import iqpilot.selfdrive.iqmodeld.daemon as iqmodeld_daemon
from iqpilot.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,188 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
import numpy as np
from iqpilot.selfdrive.iqmodeld.models.combined_artifact import resolve_combined_split_artifact
import iqpilot.selfdrive.iqmodeld.models.runners.model_runner as runner_helpers
from iqpilot.selfdrive.iqmodeld.models.runners.tinygrad import tinygrad_runner as tinygrad_runner_mod
from iqpilot.selfdrive.iqmodeld.models.runners.tinygrad.combined_split_runner import TinygradCombinedSplitRunner
from iqpilot.selfdrive.iqmodeld.models.runners.tinygrad import combined_split_runner as combined_runner_mod
from iqpilot.selfdrive.iqmodeld.models.runners.model_runner import ModelType
from iqpilot.selfdrive.iqmodeld.parser import PhaseParser
from 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("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, "_fetch_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, "_fetch_bundle", lambda: bundle)
monkeypatch.setattr(runner_helpers, "has_combined_split_artifact", lambda _: False)
monkeypatch.setattr(tinygrad_runner_mod, "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,47 @@
"""
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 iqpilot.selfdrive.iqmodeld.tools.compile_supercombo import (
_captured_devices,
_validate_pose_outputs,
)
from iqpilot.selfdrive.iqmodeld.models.runners.tinygrad.supercombo_runner import _captured_queue_depth
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,163 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
import types
import pytest
from iqpilot.cereal import log, messaging
from iqpilot.cereal.services import SERVICE_LIST
class TestTelemetryContract:
def test_service_is_published_at_stock_cadence(self):
assert "egpuDockState" in SERVICE_LIST
assert SERVICE_LIST["egpuDockState"].frequency == 10.
def test_message_carries_every_stock_field(self):
msg = messaging.new_message("egpuDockState")
state = msg.egpuDockState
for field in ("tempC", "memoryTempC", "powerDrawW", "powerLimitW", "gpuUsagePercent",
"gpuClockMhz", "fanSpeedRpm", "pcieLtssm", "supplyVoltage", "supplyCurrent"):
setattr(state, field, 1)
assert getattr(state, field) == 1
def test_metrics_refresh_matches_stock(self):
from iqpilot.selfdrive.iqmodeld.egpu_telemetry import METRICS_REFRESH_EVERY
assert METRICS_REFRESH_EVERY == 100
def test_send_without_a_gpu_publishes_an_invalid_message(self):
from iqpilot.selfdrive.iqmodeld import egpu_telemetry
sent = []
telemetry = egpu_telemetry.EgpuDockTelemetry(types.SimpleNamespace(send=lambda n, m: sent.append((n, m))), big=True)
telemetry._device = lambda: types.SimpleNamespace(_opened_devices=set())
telemetry.send()
assert sent and sent[0][0] == "egpuDockState"
assert sent[0][1].valid is False
class TestBigFrameFlag:
def test_model_message_carries_the_big_flag(self):
msg = messaging.new_message("modelV2")
msg.modelV2.big = True
assert msg.modelV2.big
class TestStatusParams:
def test_loading_param_exists_and_is_cleared_like_stock(self):
from pathlib import Path
root = Path(__file__).resolve().parents[3]
keys = (root / "common" / "params_keys.h").read_text()
assert '{"UsbGpuLoading"' in keys
line = next(ln for ln in keys.splitlines() if '"UsbGpuLoading"' in ln)
for flag in ("CLEAR_ON_MANAGER_START", "CLEAR_ON_OFFROAD_TRANSITION", "CLEAR_ON_IGNITION_ON"):
assert flag in line
class TestAlerts:
def test_both_stock_big_model_events_exist(self):
assert hasattr(log.OnroadEvent.EventName, "bigModelLoading")
assert hasattr(log.OnroadEvent.EventName, "bigModelFailed")
def test_alerts_are_wired_with_stock_severities(self):
from iqpilot.selfdrive.selfdrived.events import EVENTS, ET
EventName = log.OnroadEvent.EventName
loading = EVENTS[EventName.bigModelLoading]
failed = EVENTS[EventName.bigModelFailed]
assert ET.NO_ENTRY in loading
assert ET.SOFT_DISABLE in failed and ET.PERMANENT in failed
class TestFirmwareGate:
def test_runtime_refuses_a_dock_on_other_firmware(self, tmp_path):
from iqpilot.selfdrive.iqmodeld.egpu_helpers import usbgpu_present
from iqpilot.system.hardware.usb import EGPU_DOCK_FW_PRODUCT, EGPU_DOCK_USB_IDS
vid, pid = EGPU_DOCK_USB_IDS[0]
d = tmp_path / "1-1"
d.mkdir()
(d / "idVendor").write_text(f"{vid:04x}\n")
(d / "idProduct").write_text(f"{pid:04x}\n")
(d / "product").write_text("custom deadbeef-CLEAN\n")
assert not usbgpu_present(str(tmp_path))
(d / "product").write_text(EGPU_DOCK_FW_PRODUCT + "\n")
assert usbgpu_present(str(tmp_path))
class TestAutoFlash:
def test_hardwared_drives_the_flasher_offroad_only(self):
from iqpilot.system.hardware.hardwared import EgpuDockFlasher
f = EgpuDockFlasher()
calls = []
f.flash = lambda: calls.append(1)
stale = [{"vendorId": 0xADD1, "productId": 0x0001, "product": "custom deadbeef-CLEAN"}]
f.update(False, stale)
assert f.attempts == 0, "must not flash onroad"
f.update(True, stale)
assert f.attempts == 1
if f.thread is not None:
f.thread.join(timeout=5)
def test_matching_firmware_is_never_flashed(self):
from iqpilot.system.hardware.egpu_dock.flash import bundled_version
from iqpilot.system.hardware.hardwared import EgpuDockFlasher
f = EgpuDockFlasher()
f.flash = lambda: pytest.fail("flashed a dock that already matches")
f.update(True, [{"vendorId": 0xADD1, "productId": 0x0001, "product": bundled_version()}])
assert f.attempts == 0
def test_attempts_are_bounded_like_stock(self):
from iqpilot.system.hardware.hardwared import EgpuDockFlasher
assert EgpuDockFlasher.MAX_ATTEMPTS == 3
assert EgpuDockFlasher.RETRY_INTERVAL == 20.
class TestDockIsItsOwnConsent:
def _params(self, **flags):
class P:
def get_bool(self, k):
return bool(flags.get(k, False))
def get(self, k, *a, **kw):
return None
return P()
def _sysfs_with_dock(self, tmp_path, product=None):
from iqpilot.system.hardware.usb import EGPU_DOCK_FW_PRODUCT, EGPU_DOCK_USB_IDS
vid, pid = EGPU_DOCK_USB_IDS[0]
d = tmp_path / "1-1"
d.mkdir()
(d / "idVendor").write_text(f"{vid:04x}\n")
(d / "idProduct").write_text(f"{pid:04x}\n")
(d / "product").write_text((product or EGPU_DOCK_FW_PRODUCT) + "\n")
return str(tmp_path)
def test_a_plugged_in_dock_selects_itself(self, tmp_path):
from iqpilot.selfdrive.iqmodeld.egpu_helpers import egpu_selected
assert egpu_selected(self._params(), self._sysfs_with_dock(tmp_path))
def test_nothing_plugged_in_selects_nothing(self, tmp_path):
from iqpilot.selfdrive.iqmodeld.egpu_helpers import egpu_selected
assert not egpu_selected(self._params(), str(tmp_path))
def test_a_dock_on_foreign_firmware_does_not_select_itself(self, tmp_path):
from iqpilot.selfdrive.iqmodeld.egpu_helpers import egpu_selected
assert not egpu_selected(self._params(), self._sysfs_with_dock(tmp_path, "custom deadbeef-CLEAN"))
def test_the_user_can_force_it_off(self, tmp_path):
from iqpilot.selfdrive.iqmodeld.egpu_helpers import egpu_selected
root = self._sysfs_with_dock(tmp_path)
assert not egpu_selected(self._params(IQEgpuDisabled=True), root)
def test_the_param_can_force_it_on_without_hardware(self, tmp_path):
from iqpilot.selfdrive.iqmodeld.egpu_helpers import egpu_selected
assert egpu_selected(self._params(IQEgpuEnabled=True), str(tmp_path))
def test_present_dock_wins_even_with_emac_enabled(self, tmp_path):
from iqpilot.selfdrive.iqmodeld.egpu_helpers import egpu_selected, resolve_backend, usbgpu_present
root = self._sysfs_with_dock(tmp_path)
assert resolve_backend(True, egpu_selected(self._params(), root), usbgpu_present(root)) == "egpu"
def test_force_param_without_hardware_yields_to_emac(self, tmp_path):
from iqpilot.selfdrive.iqmodeld.egpu_helpers import egpu_selected, resolve_backend, usbgpu_present
assert resolve_backend(True, egpu_selected(self._params(IQEgpuEnabled=True), str(tmp_path)),
usbgpu_present(str(tmp_path))) == "emac"

View File

@@ -0,0 +1,509 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
from __future__ import annotations
import io
import json
import time
import urllib.request
import numpy as np
import pytest
from iqpilot.selfdrive.iqmodeld.egpu_helpers import (
resolve_backend, resolve_download_url, usbgpu_present,
)
from iqpilot.selfdrive.iqmodeld.egpu_pipeline import (
EgpuPipeline, EgpuPipelineError, make_big_channel_payload,
)
from iqpilot.selfdrive.iqmodeld.egpu_model import EGPU_MODELS, get_egpu_model
from iqpilot.selfdrive.iqmodeld.temporal_state import MODEL_INPUT_SPEC as INPUT_SPEC
class FakeParams:
def __init__(self, **flags):
self._flags = {k: bool(v) for k, v in flags.items()}
def get_bool(self, key: str) -> bool:
return self._flags.get(key, False)
def _fake_usb_device(root, vid: str, pid: str, name: str = "1-1", product: str | None = None):
from iqpilot.system.hardware.usb import EGPU_DOCK_FW_PRODUCT
d = root / name
d.mkdir()
(d / "idVendor").write_text(vid + "\n")
(d / "idProduct").write_text(pid + "\n")
(d / "product").write_text((product if product is not None else EGPU_DOCK_FW_PRODUCT) + "\n")
class TestPresence:
def test_present(self, tmp_path):
_fake_usb_device(tmp_path, "add1", "0001")
assert usbgpu_present(str(tmp_path))
def test_foreign_firmware_absent(self, tmp_path):
_fake_usb_device(tmp_path, "add1", "0001", product="custom deadbeef-CLEAN")
assert not usbgpu_present(str(tmp_path))
def test_wrong_ids_absent(self, tmp_path):
_fake_usb_device(tmp_path, "05ac", "12a8")
assert not usbgpu_present(str(tmp_path))
def test_empty_bus_absent(self, tmp_path):
assert not usbgpu_present(str(tmp_path))
def test_unreadable_entries_skipped(self, tmp_path):
(tmp_path / "usb1").mkdir()
_fake_usb_device(tmp_path, "add1", "0001", name="1-2")
assert usbgpu_present(str(tmp_path))
class TestBackendResolution:
def test_none(self):
assert resolve_backend(False, False) is None
def test_emac_only(self):
assert resolve_backend(True, False) == "emac"
def test_egpu_only(self):
assert resolve_backend(False, True) == "egpu"
def test_force_param_yields_to_emac_without_hardware(self):
assert resolve_backend(True, True) == "emac"
def test_present_dock_wins_over_emac(self):
assert resolve_backend(True, True, True) == "egpu"
class TestManagerGating:
@pytest.fixture
def pc(self):
return pytest.importorskip("iqpilot.system.manager.process_config")
def test_egpu_needs_presence(self, pc, monkeypatch):
monkeypatch.setattr(pc, "usbgpu_present", lambda: True)
assert pc.egpu_enabled(True, FakeParams(IQEgpuEnabled=True), None)
assert pc.egpu_enabled(True, FakeParams(), None)
monkeypatch.setattr(pc, "usbgpu_present", lambda: False)
assert not pc.egpu_enabled(True, FakeParams(IQEgpuEnabled=True), None)
assert not pc.egpu_enabled(True, FakeParams(), None)
def test_present_dock_wins_over_left_on_emac(self, pc, monkeypatch):
monkeypatch.setattr(pc, "usbgpu_present", lambda: True)
both = FakeParams(IQEmacEnabled=True, IQEgpuEnabled=True)
assert not pc.emac_enabled(True, both, None)
assert pc.egpu_enabled(True, both, None)
def test_emac_runs_when_no_dock(self, pc, monkeypatch):
monkeypatch.setattr(pc, "usbgpu_present", lambda: False)
assert pc.emac_enabled(True, FakeParams(IQEmacEnabled=True), None)
def test_disabled_dock_yields_to_emac(self, pc, monkeypatch):
monkeypatch.setattr(pc, "usbgpu_present", lambda: True)
both = FakeParams(IQEmacEnabled=True, IQEgpuDisabled=True)
assert pc.emac_enabled(True, both, None)
assert not pc.egpu_enabled(True, both, None)
def test_disabled_dock_runs_no_backend_when_no_emac(self, pc, monkeypatch):
monkeypatch.setattr(pc, "usbgpu_present", lambda: True)
off = FakeParams(IQEgpuDisabled=True)
assert not pc.egpu_enabled(True, off, None)
assert not pc.emac_enabled(True, off, None)
def test_selector_runs_for_either_backend(self, pc):
assert pc.big_model_enabled(True, FakeParams(IQEmacEnabled=True), None)
assert pc.big_model_enabled(True, FakeParams(IQEgpuEnabled=True), None)
assert not pc.big_model_enabled(True, FakeParams(), None)
def test_iqegpumodeld_registered(self, pc):
assert "iqegpumodeld" in pc.managed_processes
assert "maciqmodeld" in pc.managed_processes
class TestDownloadResolve:
def test_direct_url_passthrough(self):
assert resolve_download_url("https://x/y.onnx", "0" * 64, 5) == "https://x/y.onnx"
def test_commalfs_batch(self, monkeypatch):
seen = {}
def fake_urlopen(req, timeout=0):
seen["url"] = req.full_url
seen["body"] = json.loads(req.data)
return io.BytesIO(json.dumps(
{"objects": [{"actions": {"download": {"href": "https://signed/url"}}}]}).encode())
monkeypatch.setattr(urllib.request, "urlopen", fake_urlopen)
sha = "a5" * 32
url = resolve_download_url(f"commalfs:{sha}", sha, 1234)
assert url == "https://signed/url"
assert seen["body"]["objects"] == [{"oid": sha, "size": 1234}]
assert seen["url"].endswith("/info/lfs/objects/batch")
def _zero_infer(output_len: int, fill=None):
calls = []
def infer(inputs):
for name, (shape, dtype) in INPUT_SPEC.items():
assert tuple(inputs[name].shape) == shape, name
assert inputs[name].dtype == np.dtype(dtype), name
calls.append({k: v.copy() for k, v in inputs.items()})
out = np.zeros(output_len, dtype=np.float32)
if fill is not None:
out[:] = fill
return out
infer.calls = calls
return infer
def _frame_inputs(seed=0):
rng = np.random.default_rng(seed)
warped = rng.integers(0, 256, (2, 6, 128, 256)).astype(np.uint8)
desire = np.zeros(8, dtype=np.float32)
traffic = np.array([1.0, 0.0], dtype=np.float32)
action_t = np.array([0.25, 0.55], dtype=np.float32)
return warped, desire, traffic, action_t
class TestEgpuPipeline:
def setup_method(self):
self.meta = get_egpu_model()
def test_split_model_rejected(self):
split_meta = {**get_egpu_model(), "key": "some_split", "split": True}
with pytest.raises(EgpuPipelineError, match="split"):
EgpuPipeline(split_meta, _zero_infer(split_meta["output_len"]))
def test_registry_is_fused_only(self):
assert not any(m.get("split") for m in EGPU_MODELS.values())
def test_run_shapes_and_output(self):
infer = _zero_infer(self.meta["output_len"])
pipe = EgpuPipeline(self.meta, infer)
out = pipe.run(*_frame_inputs())
assert out.shape == (self.meta["output_len"],)
assert len(infer.calls) == 1
def test_hidden_state_feeds_next_features_buffer(self):
output_len = self.meta["output_len"]
hidden = self.meta["output_slices"]["hidden_state"]
def infer(inputs):
out = np.zeros(output_len, dtype=np.float32)
out[hidden] = np.arange(hidden.stop - hidden.start, dtype=np.float32)
return out
pipe = EgpuPipeline(self.meta, infer)
pipe.run(*_frame_inputs(1))
np.testing.assert_array_equal(
pipe.state.prev_feat.reshape(-1), np.arange(hidden.stop - hidden.start, dtype=np.float32))
pipe.run(*_frame_inputs(2))
np.testing.assert_array_equal(
pipe.state.feat_q[-1].reshape(-1), np.arange(hidden.stop - hidden.start, dtype=np.float32))
def test_desire_rising_edge_pulse(self):
infer = _zero_infer(self.meta["output_len"])
pipe = EgpuPipeline(self.meta, infer)
warped, _, traffic, action_t = _frame_inputs()
desire_on = np.zeros(8, dtype=np.float32)
desire_on[3] = 1.0
pipe.run(warped, desire_on, traffic, action_t)
assert infer.calls[-1]["desire_pulse"][0, -1, 3] == 1.0
for _ in range(5):
pipe.run(warped, desire_on, traffic, action_t)
assert infer.calls[-1]["desire_pulse"][0, :, 3].sum() == 1.0
def test_wrong_output_len_raises(self):
pipe = EgpuPipeline(self.meta, _zero_infer(self.meta["output_len"] - 1))
with pytest.raises(EgpuPipelineError, match="length"):
pipe.run(*_frame_inputs())
def test_non_finite_output_raises(self):
pipe = EgpuPipeline(self.meta, _zero_infer(self.meta["output_len"], fill=np.nan))
with pytest.raises(EgpuPipelineError, match="finite"):
pipe.run(*_frame_inputs())
class TestChannelContract:
def _real_msgs(self):
import iqpilot.cereal.messaging as messaging
msgs = {}
for svc in ("modelV2", "drivingModelData", "cameraOdometry", "iqDriveModelData"):
m = messaging.new_message(svc)
msgs[svc] = m.to_bytes()
return msgs
def test_payload_keys_match_selector_contract(self):
payload = make_big_channel_payload(7, True, 0.031, 24.0, {"modelV2": b"x"})
assert payload["source"] == "egpu_big"
for key in ("frame_id", "live_calib_seen", "model_execution_time", "msgs"):
assert key in payload
def test_selector_consumes_egpu_payload(self, tmp_path):
from iqpilot.selfdrive.iqmodeld.model_channel import ModelChannel
from iqpilot.selfdrive.iqmodeld.modeld_selector import wait_for_big
chan = ModelChannel(str(tmp_path / "big"), create=True)
payload = make_big_channel_payload(100, True, 0.03, 25.0, self._real_msgs())
chan.write(100, payload)
got, peek = wait_for_big(chan, 100, time.perf_counter() + 0.01)
assert peek == 100
assert got is not None
assert got["source"] == "egpu_big"
assert got["frame_id"] == 100
def test_selector_patch_and_send_parses_egpu_msgs(self):
from iqpilot.selfdrive.iqmodeld.modeld_selector import _patch_and_send
sent = {}
class PM:
def send(self, service, msg):
sent[service] = msg
payload = make_big_channel_payload(42, True, 0.03, 25.0, self._real_msgs())
_patch_and_send(PM(), payload, frame_drop_perc=0.0, selector_dropped=0, target=42, source_lag=0)
assert set(sent) == {"modelV2", "drivingModelData", "cameraOdometry", "iqDriveModelData"}
assert sent["modelV2"].modelV2.frameDropPerc == 0.0
assert sent["cameraOdometry"].valid
def test_selector_lag_patches_frame_id(self):
from iqpilot.selfdrive.iqmodeld.modeld_selector import _patch_and_send
sent = {}
class PM:
def send(self, service, msg):
sent[service] = msg
payload = make_big_channel_payload(40, True, 0.03, 25.0, self._real_msgs())
_patch_and_send(PM(), payload, frame_drop_perc=0.0, selector_dropped=0, target=42, source_lag=2)
assert sent["modelV2"].modelV2.frameId == 42
assert not sent["cameraOdometry"].valid
def _import_worker():
try:
import iqpilot.selfdrive.iqmodeld.iqegpumodeld as w
return w
except ImportError as e:
if any(tag in str(e) for tag in ("pyx", "visionipc", "proprietary_runtime")):
pytest.skip(f"device-only import chain unavailable on this host: {e}")
raise
class TestWorkerModule:
def test_module_imports_off_device(self):
w = _import_worker()
assert w.PROCESS_NAME.endswith("iqegpumodeld")
assert callable(w.main)
def test_warmup_validates_output(self):
w = _import_worker()
spec = {name: (shape, dtype) for name, (shape, dtype) in INPUT_SPEC.items()}
def good(inputs):
return np.zeros(10, dtype=np.float32)
assert w._warmup(good, spec, 10) >= 0.0
with pytest.raises(RuntimeError, match="invalid"):
w._warmup(good, spec, 11)
class TestSelectorBackendKeys:
def test_emac_default(self):
from iqpilot.selfdrive.iqmodeld.modeld_selector import EMAC_STATUS_KEYS, backend_status_keys
assert backend_status_keys(False, False) is EMAC_STATUS_KEYS
assert backend_status_keys(True, False) is EMAC_STATUS_KEYS
def test_egpu_selected(self):
from iqpilot.selfdrive.iqmodeld.modeld_selector import EGPU_STATUS_KEYS, backend_status_keys
assert backend_status_keys(False, True) is EGPU_STATUS_KEYS
assert backend_status_keys(False, True)["active"] == "UsbGpuActive"
assert backend_status_keys(False, True)["failed"] == "UsbGpuFailed"
def test_emac_wins_when_both(self):
from iqpilot.selfdrive.iqmodeld.modeld_selector import EMAC_STATUS_KEYS, backend_status_keys
assert backend_status_keys(True, True) is EMAC_STATUS_KEYS
def test_key_maps_cover_same_roles(self):
from iqpilot.selfdrive.iqmodeld.modeld_selector import EGPU_STATUS_KEYS, EMAC_STATUS_KEYS
assert set(EGPU_STATUS_KEYS) == set(EMAC_STATUS_KEYS)
class TestBackendSeparation:
EGPU_SOURCES = (
"egpu_helpers.py", "egpu_pipeline.py", "egpu_model.py", "iqegpumodeld.py",
"big_catalog.py", "tools/compile_egpu_model.py",
)
BANNED_IMPORTS = ("emac_input_state", "emac_model_meta", "maciqmodeld", "mac_protocol", "mac_client")
def _sources(self):
import pathlib
root = pathlib.Path(__file__).resolve().parents[1]
return {name: (root / name).read_text() for name in self.EGPU_SOURCES}
def test_no_emac_module_imports(self):
for name, src in self._sources().items():
for banned in self.BANNED_IMPORTS:
assert f"import {banned}" not in src and f"iqmodeld.{banned}" not in src, f"{name} imports {banned}"
def test_no_macmodel_params(self):
for name, src in self._sources().items():
assert "MacModel" not in src, f"{name} references MacModel* params"
def test_emac_shim_reexports_temporal_state(self):
from iqpilot.selfdrive.iqmodeld import emac_input_state, temporal_state
assert emac_input_state.EmacInputState is temporal_state.TemporalInputState
assert emac_input_state.SplitInputState is temporal_state.SplitTemporalState
def test_emac_modules_are_not_in_the_public_tree(self):
import pathlib
root = pathlib.Path(__file__).resolve().parents[1]
for gone in ("mac_protocol.py", "mac_client.py", "maciqmodeld.py", "bulk_transport.py"):
assert not (root / gone).exists(), f"{gone} must live only in konn3kt_private"
class TestMetaDrivenInputSpec:
def _run_one(self, meta):
seen = {}
def infer(inputs):
seen.update({k: v.shape for k, v in inputs.items()})
return np.zeros(meta["output_len"], dtype=np.float32)
pipe = EgpuPipeline(meta, infer)
pipe.run(np.zeros((2, 6, 128, 256), np.uint8), np.zeros(8, np.float32),
np.array([1, 0], np.float32), np.zeros(2, np.float32))
return seen
def test_default_contract_unchanged(self):
meta = get_egpu_model()
seen = self._run_one(meta)
assert seen["features_buffer"] == (1, 24, 512)
assert seen["desire_pulse"] == (1, 25, 8)
def test_registry_shapes_drive_the_state(self):
meta = dict(get_egpu_model())
meta["output_len"] = 18452
meta["output_slices"] = dict(meta["output_slices"], hidden_state=slice(2066, 18450))
meta["input_shapes"] = {
"img": (1, 12, 128, 256), "big_img": (1, 12, 128, 256),
"desire_pulse": (1, 33, 8), "traffic_convention": (1, 2),
"action_t": (1, 2), "features_buffer": (1, 32, 32, 512),
}
seen = self._run_one(meta)
assert seen["features_buffer"] == (1, 32, 32, 512)
assert seen["desire_pulse"] == (1, 33, 8)
class TestCatalogResolution:
def _params(self, model, doc=None):
class P:
def get(self, k):
if k == "IQEmacModel":
return model
if k == "IQEmacCatalogCache":
return json.dumps(doc) if doc else None
return None
return P()
def _doc(self):
return {"schema": 1, "bundles": [{
"short_name": "ttx", "display_name": "TTx", "index": 1,
"model_name": "big_driving_supercombo",
"wire": {"output_len": 2580, "frame_skip": 4, "pipeline": True,
"output_slices": {"plan": [917, 1907], "hidden_state": [2066, 2578], "pad": [-2, None]},
"input_shapes": {"img": [1, 12, 128, 256], "big_img": [1, 12, 128, 256],
"desire_pulse": [1, 33, 8], "traffic_convention": [1, 2],
"action_t": [1, 2], "features_buffer": [1, 32, 512]},
"lat_smooth_seconds": 0.1},
"source": {"kind": "comma_lfs", "sha256": "c" * 64, "size": 1},
}]}
def test_unset_selection_is_the_builtin_default(self):
from iqpilot.selfdrive.iqmodeld.egpu_model import resolve_egpu_model
m = resolve_egpu_model(self._params(None))
assert m["key"] == "lebrowski" and m["sha256"].startswith("a501760a")
def test_catalog_selection_resolves_with_shapes_and_smoothing(self):
from iqpilot.selfdrive.iqmodeld.egpu_model import resolve_egpu_model
m = resolve_egpu_model(self._params("ttx", self._doc()))
assert m["key"] == "ttx"
assert m["input_shapes"]["features_buffer"] == (1, 32, 512)
assert m["input_shapes"]["desire_pulse"] == (1, 33, 8)
assert m["lat_smooth_seconds"] == 0.1
assert m["output_slices"]["pad"] == slice(-2, None)
def test_unknown_selection_is_a_park_not_a_silent_default(self):
from iqpilot.selfdrive.iqmodeld.egpu_model import resolve_egpu_model
assert resolve_egpu_model(self._params("ghost", self._doc()), allow_refresh=False) is None
def test_bench_model_is_not_selectable(self):
from iqpilot.selfdrive.iqmodeld.egpu_model import resolve_egpu_model
assert resolve_egpu_model(self._params("comma_small", self._doc()), allow_refresh=False) is None
def test_registry_carries_no_model_list(self):
from iqpilot.selfdrive.iqmodeld.egpu_model import EGPU_MODELS
assert set(EGPU_MODELS) == {"lebrowski", "comma_small"}
class TestConsentAndIntegrity:
def test_disabled_param_denies_present_dock(self, monkeypatch):
from iqpilot.selfdrive.iqmodeld import egpu_helpers
monkeypatch.setattr(egpu_helpers, "usbgpu_present", lambda sysfs_root=egpu_helpers.USB_SYSFS_ROOT: True)
assert egpu_helpers.egpu_present_consented(FakeParams()) is True
assert egpu_helpers.egpu_present_consented(FakeParams(IQEgpuDisabled=True)) is False
def test_local_onnx_quarantines_bad_content(self, tmp_path, monkeypatch):
import hashlib
from iqpilot.selfdrive.iqmodeld import egpu_helpers
onnx = tmp_path / "m.onnx"
onnx.write_bytes(b"good")
meta = {"sha256": hashlib.sha256(b"good").hexdigest(), "download": {"size": 4}}
monkeypatch.setattr(egpu_helpers, "onnx_cache_path", lambda m: str(onnx))
assert egpu_helpers.local_onnx(meta) == str(onnx)
onnx.write_bytes(b"bad!")
assert egpu_helpers.local_onnx(meta) is None
assert not onnx.exists()
assert (tmp_path / "m.onnx.unusable").exists()
class TestEgpuDockStatus:
def _run(self, seq):
from iqpilot.system.hardware.egpu_dock.status import EgpuDockStatus
st = EgpuDockStatus()
fired = {}
def set_alert(name, cond, extra=None):
fired[name] = (bool(cond), extra)
for args in seq:
st.update(*args, set_alert)
return {k: v for k, v in fired.items() if v[0]}
def _dock(self, speed=10000, product="custom ed4e39b7-CLEAN"):
return [{"vendorId": 0xADD1, "productId": 0x0001, "product": product, "speedMbps": speed}]
def test_no_dock_no_alerts(self):
assert self._run([(True, [], False, False, None, True, None)]) == {}
def test_usb2_dock_warns_slow(self):
fired = self._run([(True, self._dock(speed=480), False, False, None, True, None)])
assert fired.get("Offroad_EgpuUsbSlow") == (True, "480 Mbps")
def test_power_fault_reports_pcie_unavailable(self):
class St:
supplyFault = True
supplyVoltage = 0
pcieLtssm = 0x78
tempC = memoryTempC = 40.0
fanSpeedRpm = 1500
d = self._dock()
fired = self._run([
(True, d, False, False, None, True, None),
(False, d, False, True, None, True, None),
(False, d, False, False, b"1", True, St()),
])
assert "Offroad_EgpuPcieUnavailable" in fired

View File

@@ -0,0 +1,111 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
from __future__ import annotations
import os
import numpy as np
import pytest
os.environ.setdefault("DEV", "CPU")
from iqpilot.selfdrive.iqmodeld.emac_input_state import EmacInputState
from iqpilot.selfdrive.iqmodeld.emac_model_meta import FRAME_SKIP, OUTPUT_LEN, OUTPUT_SLICES
from iqpilot.selfdrive.iqmodeld.temporal_state import MODEL_INPUT_SPEC as INPUT_SPEC
N_FRAMES_TEST = 30
IMG_SHAPE = INPUT_SPEC["img"][0]
DESIRE_LEN = INPUT_SPEC["desire_pulse"][0][2]
class _CaptureRunner:
def __init__(self):
self.captured: dict[str, np.ndarray] | None = None
def __call__(self, inputs):
from tinygrad import Tensor
self.captured = {k: v.numpy().copy() for k, v in inputs.items()}
return {"outputs": Tensor(np.zeros((1, OUTPUT_LEN), dtype=np.float32))}
@pytest.fixture(scope="module")
def reference():
from iqpilot.selfdrive.iqmodeld.tools.compile_supercombo import (
POLICY_INPUTS, make_input_queues, make_run_policy,
)
input_shapes = {name: shape for name, (shape, _) in INPUT_SPEC.items()}
metadata = {"input_shapes": input_shapes}
capture = _CaptureRunner()
run_policy = make_run_policy(capture, metadata, FRAME_SKIP)
queues, npy = make_input_queues(input_shapes, FRAME_SKIP, device="CPU")
return run_policy, queues, npy, capture, POLICY_INPUTS
def _rising_edge(raw_desire: np.ndarray, prev: np.ndarray) -> np.ndarray:
cur = raw_desire.astype(np.float32).copy()
cur[0] = 0
pulse = np.where(cur - prev > 0.99, cur, 0).astype(np.float32)
prev[:] = cur
return pulse
def test_materialized_inputs_match_tinygrad_reference(reference):
from tinygrad import Tensor
run_policy, queues, npy, capture, policy_inputs = reference
rng = np.random.default_rng(1234)
state = EmacInputState(FRAME_SKIP)
ref_prev_desire = np.zeros(DESIRE_LEN, dtype=np.float32)
hidden = np.zeros((1, 512), dtype=np.float32)
for frame in range(N_FRAMES_TEST):
warped = rng.integers(0, 256, (2, 6, IMG_SHAPE[2], IMG_SHAPE[3]), dtype=np.int64).astype(np.uint8)
raw_desire = np.zeros(DESIRE_LEN, dtype=np.float32)
if frame % 3:
raw_desire[int(rng.integers(0, DESIRE_LEN))] = 1.0
traffic = rng.standard_normal(2).astype(np.float32)
action_t = rng.standard_normal(2).astype(np.float32)
npy["desire"][:] = _rising_edge(raw_desire, ref_prev_desire)
npy["traffic_convention"][:] = traffic
npy["action_t"][:] = action_t
npy["prev_feat"][:] = hidden
run_policy(warped=Tensor(warped), **{k: queues[k] for k in policy_inputs})
ref_inputs = capture.captured
state.prev_feat[:] = hidden
mat = state.push_and_materialize(warped, raw_desire, traffic, action_t)
for name in INPUT_SPEC:
assert ref_inputs[name].shape == tuple(INPUT_SPEC[name][0]), name
np.testing.assert_array_equal(
mat[name].astype(ref_inputs[name].dtype), ref_inputs[name],
err_msg=f"frame {frame}: materialized {name} diverges from tinygrad reference")
fake_output = rng.standard_normal(OUTPUT_LEN).astype(np.float32)
state.note_hidden_state(fake_output, OUTPUT_SLICES["hidden_state"])
hidden = fake_output[OUTPUT_SLICES["hidden_state"]].reshape(1, 512).copy()
def test_note_hidden_state_slice():
state = EmacInputState(FRAME_SKIP)
out = np.arange(OUTPUT_LEN, dtype=np.float32)
state.note_hidden_state(out, OUTPUT_SLICES["hidden_state"])
np.testing.assert_array_equal(state.prev_feat.reshape(-1), out[OUTPUT_SLICES["hidden_state"]])
def test_desire_pulse_rising_edge_only_once():
state = EmacInputState(FRAME_SKIP)
held = np.zeros(DESIRE_LEN, dtype=np.float32)
held[3] = 1.0
warped = np.zeros((2, 6, IMG_SHAPE[2], IMG_SHAPE[3]), dtype=np.uint8)
zeros2 = np.zeros(2, dtype=np.float32)
first = state.push_and_materialize(warped, held, zeros2, zeros2)
assert first["desire_pulse"][0, -1, 3] == 1.0
second = state.push_and_materialize(warped, held, zeros2, zeros2)
assert state.desire_q[-1].max() == 0.0
assert second["desire_pulse"][0, -1, 3] == 1.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 pickle
from dataclasses import dataclass
import numpy as np
import pytest
from iqpilot.selfdrive.iqmodeld.models.runners import model_runner as model_runner_mod
from iqpilot.selfdrive.iqmodeld.models.runners.model_runner import ModelType
from iqpilot.selfdrive.iqmodeld.models.runners.tinygrad import fused_runner as fused_mod
class _View:
def __init__(self, shape):
self.shape = shape
class _Captured:
def __init__(self, expected_names, expected_input_info):
self.expected_names = expected_names
self.expected_input_info = expected_input_info
class _FakeJit:
def __init__(self, expected_names, expected_input_info):
self.captured = _Captured(expected_names, expected_input_info)
def __call__(self, **kwargs):
raise AssertionError("policy jit should not run in this test")
class _FakeTensor:
def __init__(self, arr, device=None):
self.shape = tuple(np.asarray(arr).shape)
def contiguous(self):
return self
def realize(self):
return self
class _FakeDevice:
DEFAULT = "FAKE"
@dataclass
class _Type:
raw: int
@dataclass
class _Artifact:
fileName: str
class _Model:
def __init__(self, file_name):
self.type = _Type(ModelType.vision)
self.artifact = _Artifact(file_name)
self.metadata = None
class _Bundle:
def __init__(self, file_name):
self.models = [_Model(file_name)]
self.is20hz = True
POLICY_INPUTS = ["action_t", "big_img", "desire", "desire_q", "feat_q", "img", "traffic_convention"]
POLICY_SHAPES = {
"action_t": (1, 2), "big_img": (1, 12, 128, 256), "desire": (1, 8), "desire_q": (1, 100, 8),
"feat_q": (1, 99, 512), "img": (1, 12, 128, 256), "traffic_convention": (1, 2),
}
def _write_fused_pkl(path, policy_inputs):
info = [(_View(POLICY_SHAPES[n]), (), None, "NPY") for n in policy_inputs]
role_meta = {
"input_shapes": {"desire_pulse": (1, 100, 8), "traffic_convention": (1, 2), "features_buffer": (1, 99, 512)},
"output_slices": {},
}
blob = {
"metadata": {
"vision": {"input_shapes": {"img": (1, 12, 128, 256), "big_img": (1, 12, 128, 256)}, "output_slices": {}},
"on_policy": role_meta,
"off_policy": role_meta,
},
"run_policy": _FakeJit(policy_inputs, info),
"frame_skip": 4,
(1928, 1208): _FakeJit(["frame"], [(_View((1,)), (), None, "NPY")]),
}
with open(path, "wb") as f:
pickle.dump(blob, f)
@pytest.fixture
def fused_runner(tmp_path, monkeypatch):
def _build(policy_inputs):
name = "driving_fused_test.pkl"
_write_fused_pkl(tmp_path / name, policy_inputs)
monkeypatch.setattr(model_runner_mod, "_fetch_bundle", lambda params=None: _Bundle(name))
monkeypatch.setattr(fused_mod, "CUSTOM_MODEL_PATH", str(tmp_path))
monkeypatch.setattr(fused_mod, "_tinygrad_imports", lambda: (_FakeTensor, _FakeDevice))
return fused_mod.TinygradFusedRunner()
return _build
def test_action_t_allocated_when_only_the_jit_declares_it(fused_runner):
runner = fused_runner(POLICY_INPUTS)
assert "action_t" not in runner._on_meta["input_shapes"]
runner._ensure_queues(1928, 1208)
assert runner._npy_buffers["action_t"].shape == POLICY_SHAPES["action_t"]
assert runner._npy_buffers["traffic_convention"].shape == POLICY_SHAPES["traffic_convention"]
def test_action_t_absent_when_the_jit_does_not_take_it(fused_runner):
runner = fused_runner([n for n in POLICY_INPUTS if n != "action_t"])
runner._ensure_queues(1928, 1208)
assert "action_t" not in runner._npy_buffers

View File

@@ -0,0 +1,126 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
from __future__ import annotations
import copy
import iqpilot.cereal.messaging as messaging
import numpy as np
from iqpilot.cereal import log
from iqpilot.selfdrive.iqmodeld.config import Meta, ModelConstants
from iqpilot.selfdrive.iqmodeld.messaging import (
DrivePacketMemory,
pick_curvature,
populate_drive_messages,
populate_odometry_message,
)
from iqpilot.selfdrive.iqmodeld.models.split_model_constants import SplitModelConstants
from 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,73 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
"""
from types import SimpleNamespace
import pytest
from iqpilot.cereal import car
from iqpilot.common.params import Params
from iqpilot.selfdrive.iqmodeld.daemon import InferenceDaemon
LIVE_DELAY = 0.4387
RACK_DELAY = 0.10
OFFSET = 0.05
@pytest.fixture
def params(tmp_path, monkeypatch):
monkeypatch.setenv("PARAMS_ROOT", str(tmp_path))
p = Params()
p.put("IQSteerDelayCache", LIVE_DELAY)
p.put("IQSoftwareSteerDelay", OFFSET)
p.put_bool("ModelSmoothingEnabled", False)
p.put("ModelLatSmoothSec", 0)
p.put("PlanplusControl", 1.0)
p.put("CameraOffset", 0.0)
return p
def _daemon(params, steer_control_type):
car_params = car.CarParams.new_message()
car_params.steerControlType = steer_control_type
car_params.steerActuatorDelay = RACK_DELAY
return SimpleNamespace(
_params=params,
_car_params=car_params,
_channel=None,
_sub={"lateralDelay": SimpleNamespace(lateralDelay=LIVE_DELAY)},
_runtime=SimpleNamespace(lat_delay=None, PLANPLUS_CONTROL=None, model_smoothing_max_extra_sec=None),
_warps=SimpleNamespace(set_offset=lambda _: None),
)
@pytest.mark.parametrize("live_enabled, expected", [(False, RACK_DELAY + OFFSET), (True, LIVE_DELAY)])
def test_angle_cars_honour_the_self_tuning_toggle(params, live_enabled, expected):
params.put_bool("IQLiveSteerDelay", live_enabled)
daemon = _daemon(params, car.CarParams.SteerControlType.angle)
InferenceDaemon._refresh_tunables(daemon, 0)
assert daemon._runtime.lat_delay == pytest.approx(expected)
def test_angle_cars_never_plan_against_the_live_estimate_when_disabled(params):
params.put_bool("IQLiveSteerDelay", False)
daemon = _daemon(params, car.CarParams.SteerControlType.angle)
InferenceDaemon._refresh_tunables(daemon, 0)
assert daemon._runtime.lat_delay != pytest.approx(LIVE_DELAY)
@pytest.mark.parametrize("live_enabled", [True, False])
def test_torque_cars_keep_the_live_estimate(params, live_enabled):
params.put_bool("IQLiveSteerDelay", live_enabled)
daemon = _daemon(params, car.CarParams.SteerControlType.torque)
InferenceDaemon._refresh_tunables(daemon, 0)
assert daemon._runtime.lat_delay == pytest.approx(LIVE_DELAY)
def test_refresh_is_throttled_to_every_sixtieth_tick(params):
params.put_bool("IQLiveSteerDelay", False)
daemon = _daemon(params, car.CarParams.SteerControlType.angle)
InferenceDaemon._refresh_tunables(daemon, 1)
assert daemon._runtime.lat_delay is None
InferenceDaemon._refresh_tunables(daemon, 60)
assert daemon._runtime.lat_delay == pytest.approx(RACK_DELAY + OFFSET)

View File

@@ -0,0 +1,77 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
import numpy as np
import pytest
from tinygrad.tensor import Tensor
import iqpilot.selfdrive.iqmodeld.models.helpers as bundle_helpers
import iqpilot.selfdrive.iqmodeld.models.runners.model_runner as model_runner_mod
import iqpilot.selfdrive.iqmodeld.models.runners.tinygrad.tinygrad_runner as tinygrad_runner_mod
from iqpilot.selfdrive.iqmodeld.models.runners.model_runner import ModelType
from 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()
@pytest.mark.tici
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, "_fetch_bundle", lambda params=None: bundle)
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,21 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
from iqpilot.selfdrive.iqmodeld import metadata, messaging, parser
from 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,171 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
from __future__ import annotations
import os
from dataclasses import dataclass
from pathlib import Path
import numpy as np
from tinygrad.nn.onnx import OnnxRunner
from tinygrad.tensor import Tensor
import iqpilot.selfdrive.iqmodeld.models.helpers as bundle_helpers
import iqpilot.selfdrive.iqmodeld.models.runners.model_runner as model_runner_mod
import iqpilot.selfdrive.iqmodeld.models.runners.tinygrad.tinygrad_runner as tinygrad_runner_mod
from iqpilot.selfdrive.iqmodeld.models.runners.model_runner import ModelType
from 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(model_runner_mod, "_fetch_bundle", lambda: bundle)
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(),
)
def test_three_selector_models_parse_via_share_onnx():
if not SHARE_ROOT.is_dir():
return
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
def test_selector_tinygrad_pkls_execute_when_host_compatible(monkeypatch):
if not SHARE_ROOT.is_dir():
return
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
except TypeError as exc:
if "DType.__init__()" in str(exc):
continue
raise
assert "pose" in vision_outputs
assert "plan" in policy_outputs
executed += 1
if executed >= 3:
break
if executed == 0:
assert attempted > 0, "no selector bundles were inspected on the share"

View File

@@ -0,0 +1,31 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
The eMac bundles ship `minimum_selector_version = 17`, and the version
gate lives in the COMPILED private selector bundle, not in this repo. If that
bundle is rebuilt from stale source the gate still reads 16, every eMac bundle
is silently dropped as "too new", and the selector simply shows no eMac models
— with no error anywhere. Assert the effective gate instead, so a stale
private bundle fails here rather than on a device.
"""
from iqpilot.selfdrive.iqmodeld.emac_model_meta import EMAC_BUNDLE_MIN_SELECTOR_VERSION
from iqpilot.selfdrive.iqmodeld.models.helpers import is_bundle_version_compatible
def test_gate_accepts_the_version_our_emac_bundles_ship():
assert is_bundle_version_compatible({"minimumSelectorVersion": EMAC_BUNDLE_MIN_SELECTOR_VERSION}), (
f"the effective selector gate rejects minimumSelectorVersion="
f"{EMAC_BUNDLE_MIN_SELECTOR_VERSION}; the private selector bundle is stale. "
f"Rebuild it from BOTH iqpilot/models_private_src/helpers.py "
f"(CURRENT_SELECTOR_VERSION) and fetcher.py (MANIFEST_VERSION)."
)
def test_gate_still_accepts_older_bundles():
# the window is a range, not a floor: bumping it must not orphan the existing catalogue
assert is_bundle_version_compatible({"minimumSelectorVersion": 12})
assert is_bundle_version_compatible({"minimumSelectorVersion": 16})
def test_gate_rejects_a_bundle_from_the_future():
assert not is_bundle_version_compatible({"minimumSelectorVersion": EMAC_BUNDLE_MIN_SELECTOR_VERSION + 5})

View File

@@ -0,0 +1,131 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
eMac split-model "prepared input equivalence": SplitInputState must reproduce,
byte-exact, the queue semantics of compile_split_runtime's execute_bundle —
the real tinygrad reference graph run on CPU with stub vision/policy runners,
over a multi-frame random sequence with desire rising edges.
"""
from __future__ import annotations
import os
import numpy as np
import pytest
os.environ.setdefault("DEV", "CPU")
from iqpilot.selfdrive.iqmodeld.emac_input_state import EmacInputState, SplitInputState
N_FRAMES_TEST = 30
FRAME_SKIP = 4
IMG_SHAPE = (1, 12, 16, 32) # small spatial dims: queue math is shape-generic
FB_SHAPE = (1, 25, 512)
DP_SHAPE = (1, 25, 8)
VISION_OUT_LEN = 1576
HIDDEN_SLICE = slice(1064, 1576)
VISION_SHAPES = {"img": IMG_SHAPE, "big_img": IMG_SHAPE}
POLICY_SHAPES = {"desire_pulse": DP_SHAPE, "traffic_convention": (1, 2), "features_buffer": FB_SHAPE}
class _StubRunner:
"""Stands in for OnnxRunner inside execute_bundle: returns a preset output
and records the materialized inputs it was fed."""
def __init__(self, out_len: int):
self.out_len = out_len
self.next_output: np.ndarray | None = None
self.captured: dict[str, np.ndarray] | None = None
def __call__(self, inputs):
from tinygrad import Tensor
self.captured = {k: v.numpy().copy() for k, v in inputs.items()}
out = self.next_output if self.next_output is not None else np.zeros((1, self.out_len), dtype=np.float32)
return {"outputs": Tensor(out.astype(np.float32))}
@pytest.fixture(scope="module")
def reference():
from tinygrad import Tensor
from iqpilot.selfdrive.iqmodeld.tools.compile_split_runtime import _role_executor
meta_by_role = {
"vision": {"input_shapes": dict(VISION_SHAPES), "output_slices": {"hidden_state": HIDDEN_SLICE}},
"policy": {"input_shapes": dict(POLICY_SHAPES), "output_slices": {}},
}
vision, policy = _StubRunner(VISION_OUT_LEN), _StubRunner(1000)
execute_bundle = _role_executor({"vision": vision, "policy": policy}, meta_by_role, FRAME_SKIP)
feat_q = Tensor(np.zeros((FRAME_SKIP * (FB_SHAPE[1] - 1) + 1, FB_SHAPE[0], FB_SHAPE[2]), dtype=np.float32),
device="CPU").contiguous().realize()
desire_q = Tensor(np.zeros((FRAME_SKIP * DP_SHAPE[1], DP_SHAPE[0], DP_SHAPE[2]), dtype=np.float32),
device="CPU").contiguous().realize()
return execute_bundle, feat_q, desire_q, vision, policy
def test_split_inputs_match_tinygrad_reference(reference):
from tinygrad import Tensor
execute_bundle, feat_q, desire_q, vision_stub, policy_stub = reference
rng = np.random.default_rng(4321)
state = SplitInputState(FRAME_SKIP, IMG_SHAPE, FB_SHAPE, DP_SHAPE)
ref_prev_desire = np.zeros(DP_SHAPE[2], dtype=np.float32)
for frame in range(N_FRAMES_TEST):
warped = rng.integers(0, 256, (2, 6, IMG_SHAPE[2], IMG_SHAPE[3]), dtype=np.int64).astype(np.uint8)
raw_desire = np.zeros(DP_SHAPE[2], dtype=np.float32)
if frame % 3:
raw_desire[int(rng.integers(0, DP_SHAPE[2]))] = 1.0
traffic = rng.standard_normal((1, 2)).astype(np.float32)
vision_out = rng.standard_normal((1, VISION_OUT_LEN)).astype(np.float32)
vision_stub.next_output = vision_out
# --- ours ---
vis_inputs = state.materialize_vision(warped, raw_desire)
pol_inputs = state.materialize_policy(vision_out[0, HIDDEN_SLICE], traffic[0])
# --- reference graph: rising edge happens outside execute_bundle (run_fused) ---
cur = raw_desire.copy()
cur[0] = 0
ref_pulse = np.where(cur - ref_prev_desire > 0.99, cur, 0).astype(np.float32)
ref_prev_desire[:] = cur
execute_bundle(
img=Tensor(vis_inputs["img"], device="CPU").realize(),
big_img=Tensor(vis_inputs["big_img"], device="CPU").realize(),
feat_q=feat_q, desire_q=desire_q,
desire=Tensor(ref_pulse, device="CPU").realize(),
traffic_convention=Tensor(traffic, device="CPU").realize(),
action_t=Tensor(np.zeros((1, 2), dtype=np.float32), device="CPU").realize(),
)
ref = policy_stub.captured
assert ref is not None
assert ref["features_buffer"].tobytes() == pol_inputs["features_buffer"].tobytes(), f"features frame {frame}"
assert ref["desire_pulse"].tobytes() == pol_inputs["desire_pulse"].tobytes(), f"desire frame {frame}"
assert ref["traffic_convention"].tobytes() == pol_inputs["traffic_convention"].tobytes()
# vision saw exactly what our img queues materialized
vref = vision_stub.captured
assert vref["img"].tobytes() == vis_inputs["img"].tobytes(), f"img frame {frame}"
assert vref["big_img"].tobytes() == vis_inputs["big_img"].tobytes(), f"big_img frame {frame}"
def test_split_img_queue_matches_fused_state():
# img/desire mechanics are shared with the fused mirror: same warps must
# materialize identical img/big_img in both states
rng = np.random.default_rng(7)
fused_spec = {
"img": (IMG_SHAPE, "uint8"), "big_img": (IMG_SHAPE, "uint8"),
"desire_pulse": (DP_SHAPE, "float32"), "traffic_convention": ((1, 2), "float32"),
"features_buffer": ((1, 24, 512), "float32"), "action_t": ((1, 2), "float32"),
}
fused = EmacInputState(FRAME_SKIP, fused_spec)
split = SplitInputState(FRAME_SKIP, IMG_SHAPE, FB_SHAPE, DP_SHAPE)
for _ in range(12):
warped = rng.integers(0, 256, (2, 6, IMG_SHAPE[2], IMG_SHAPE[3]), dtype=np.int64).astype(np.uint8)
desire = np.zeros(DP_SHAPE[2], dtype=np.float32)
f = fused.push_and_materialize(warped, desire, np.zeros(2, dtype=np.float32), np.zeros(2, dtype=np.float32))
s = split.materialize_vision(warped, desire)
assert f["img"].tobytes() == s["img"].tobytes()
assert f["big_img"].tobytes() == s["big_img"].tobytes()

View File

@@ -0,0 +1,254 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
from __future__ import annotations
import hashlib
from pathlib import Path
import pytest
from iqpilot.cereal import custom
from iqpilot.selfdrive.iqmodeld.models import helpers as model_helpers
from iqpilot.selfdrive.iqmodeld.models.runners.tinygrad import supercombo_runner as supercombo_runner_mod
from 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_seed_default_bundle_runs_while_a_download_is_queued(monkeypatch: pytest.MonkeyPatch):
monkeypatch.setattr(model_helpers, "ensure_default_model_files", lambda *a, **k: None)
params = _FakeParams()
params.put("ModelManager_DownloadIndex", "81")
model_helpers.seed_default_bundle_if_unset(params)
active = params.get("ModelManager_ActiveBundle")
assert active is not None and active.get("ref") == "default"
assert params.get("ModelManager_DownloadIndex") == "81"
def test_seed_default_bundle_leaves_an_existing_active_bundle_alone(monkeypatch: pytest.MonkeyPatch):
monkeypatch.setattr(model_helpers, "ensure_default_model_files", lambda *a, **k: None)
params = _FakeParams({"index": 81, "ref": "pop"})
params.put("ModelManager_DownloadIndex", "81")
model_helpers.seed_default_bundle_if_unset(params)
assert params.get("ModelManager_ActiveBundle").get("ref") == "pop"
assert params.get("ModelManager_DownloadIndex") == "81"
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,155 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
from __future__ import annotations
from dataclasses import dataclass
from types import SimpleNamespace
import numpy as np
import pytest
import iqpilot.selfdrive.iqmodeld.models.helpers as bundle_helpers
import iqpilot.selfdrive.iqmodeld.models.runners.model_runner as runner_helpers
import 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 iqpilot.cereal.messaging as messaging
from iqpilot.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 iqpilot.selfdrive.iqmodeld.tools.daemon_jit_compiler import main
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -0,0 +1,144 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
from __future__ import annotations
import argparse
import os
import pickle
import time
os.environ.setdefault("DEV", "USB+AMD:LLVM")
os.environ.setdefault("FLOAT16", "1")
os.environ.setdefault("JIT_BATCH_SIZE", "0")
os.environ.setdefault("GMMU", "0")
import numpy as np
from iqpilot.selfdrive.iqmodeld.egpu_helpers import egpu_pkl_path, local_onnx, patch_tinygrad_fetch_fw
from iqpilot.selfdrive.iqmodeld.egpu_model import EGPU_MODELS, get_egpu_model, resolve_egpu_model
from iqpilot.selfdrive.iqmodeld.temporal_state import MODEL_INPUT_SPEC, spec_from_meta
INPUT_SPEC = dict(MODEL_INPUT_SPEC)
patch_tinygrad_fetch_fw()
SEED = 42
def set_input_spec(meta: dict) -> None:
spec = spec_from_meta(meta)
if spec is not None:
INPUT_SPEC.clear()
INPUT_SPEC.update(spec)
def make_run_model(model_runner):
def run_model(**inputs):
out = next(iter(model_runner({k: inputs[k] for k in INPUT_SPEC}).values())).cast("float32")
return out.reshape(-1),
return run_model
def _random_inputs(seed: int):
from tinygrad.device import Device
from tinygrad.tensor import Tensor
rng = np.random.default_rng(seed)
out = {}
for name, (shape, dtype) in INPUT_SPEC.items():
if dtype == "uint8":
arr = rng.integers(0, 256, shape).astype(np.uint8)
else:
arr = rng.standard_normal(shape).astype(np.float32)
out[name] = Tensor(arr, device=Device.DEFAULT).realize()
return out
def _run(fn, seed: int) -> np.ndarray:
from tinygrad.device import Device
st = time.perf_counter()
outs = fn(**_random_inputs(seed))
Device.default.synchronize()
print(f" run(seed={seed}) {(time.perf_counter() - st) * 1e3:6.1f} ms")
return outs[0].numpy().reshape(-1)
def compile_model(meta: dict, onnx_path: str, out_path: str) -> str:
from tinygrad.device import Device
from tinygrad.engine.jit import TinyJit
from tinygrad.nn.onnx import OnnxRunner
if meta.get("split"):
raise RuntimeError(f"model {meta['key']} is a split model; eGPU v1 compiles fused models only")
jit = TinyJit(make_run_model(OnnxRunner(onnx_path)), prune=True)
print("capture + replay")
for _ in range(2):
baseline = _run(jit, SEED)
if baseline.shape[0] != meta["output_len"]:
raise RuntimeError(f"model output length {baseline.shape[0]} != registry {meta['output_len']}")
if not np.isfinite(baseline).all():
raise RuntimeError("compiled model produced non-finite outputs")
print("pickle round trip")
jit = pickle.loads(pickle.dumps(jit))
if not np.array_equal(_run(jit, SEED), baseline):
raise RuntimeError("outputs differ from baseline after pickle round trip")
if np.array_equal(_run(jit, SEED + 1), baseline):
raise RuntimeError("outputs insensitive to inputs after pickle round trip")
from tinygrad.tensor import Tensor
zeros = {name: Tensor(np.zeros(shape, dtype=dtype), device=Device.DEFAULT).realize()
for name, (shape, dtype) in INPUT_SPEC.items()}
flat = jit(**zeros)[0].numpy().reshape(-1)
from iqpilot.selfdrive.iqmodeld.parser import PhaseParser
from iqpilot.selfdrive.iqmodeld.tools.compile_supercombo import _slice_outputs, _validate_pose_outputs
_validate_pose_outputs(PhaseParser().parse_vision_outputs(_slice_outputs(flat, meta["output_slices"])))
bundle = {
"run_model": jit,
"model_key": meta["key"],
"model_sha256": meta["sha256"],
"output_len": int(meta["output_len"]),
"frame_skip": int(meta["frame_skip"]),
"input_spec": {name: (tuple(shape), dtype) for name, (shape, dtype) in INPUT_SPEC.items()},
"input_device": Device.DEFAULT,
}
os.makedirs(os.path.dirname(out_path), exist_ok=True)
tmp = out_path + ".part"
with open(tmp, "wb") as f:
pickle.dump(bundle, f, protocol=pickle.HIGHEST_PROTOCOL)
os.replace(tmp, out_path)
return out_path
def main() -> None:
p = argparse.ArgumentParser()
p.add_argument("--model", default=None, help=f"registry key, one of {sorted(EGPU_MODELS)}")
p.add_argument("--onnx", default=None)
p.add_argument("--output", default=None)
args = p.parse_args()
if args.model is not None:
if args.model in EGPU_MODELS:
meta = get_egpu_model(args.model)
else:
from iqpilot.common.params import Params
meta = resolve_egpu_model(Params(), args.model)
if meta is None:
raise SystemExit(f"unknown model {args.model!r}: not a built-in ({sorted(EGPU_MODELS)}) and not in the synced catalog")
else:
meta = get_egpu_model()
set_input_spec(meta)
onnx_path = args.onnx or local_onnx(meta)
if onnx_path is None or not os.path.isfile(onnx_path):
raise SystemExit(f"onnx not found for {meta['key']}; pass --onnx or let iqegpumodeld download it first")
out = compile_model(meta, onnx_path, args.output or egpu_pkl_path(meta))
print(f"saved eGPU jit to {out} ({os.path.getsize(out) / 1e6:.2f} MB)")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,9 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
from iqpilot.selfdrive.iqmodeld.tools.compile_warp import MODEL_SIZE, compile_warp, main
__all__ = ["MODEL_SIZE", "compile_warp", "main"]
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,125 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
import os
import pickle
import re
import shutil
import sys
import time
import numpy as np
if "JIT_BATCH_SIZE" not in os.environ:
os.environ["JIT_BATCH_SIZE"] = "0"
from tinygrad import Context, Device, GlobalCounters, Tensor, TinyJit, dtypes
from tinygrad.helpers import DEBUG, getenv
from tinygrad.nn.onnx import OnnxRunner
from tinygrad.uop.ops import Ops
def compile_model(onnx_file, output):
run_onnx = OnnxRunner(onnx_file)
print("loaded model")
input_shapes = {name: spec.shape for name, spec in run_onnx.graph_inputs.items()}
input_types = {name: spec.dtype for name, spec in run_onnx.graph_inputs.items()}
input_types = {key: dtypes.float32 if value is dtypes.float16 else value for key, value in input_types.items()}
input_shapes = {key: tuple(value if isinstance(value, int) else 1 for value in shape) for key, shape in input_shapes.items()}
Tensor.manual_seed(100)
inputs = {
key: Tensor(Tensor.randn(*shape, dtype=input_types[key]).mul(8).realize().numpy(), device="NPY")
for key, shape in sorted(input_shapes.items())
}
if not getenv("NPY_IMG"):
inputs = {key: Tensor(value.numpy(), device=Device.DEFAULT).realize() if "img" in key else value for key, value in inputs.items()}
print("created tensors")
run_onnx_jit = TinyJit(
lambda **kwargs: next(iter(run_onnx({key: value.to(Device.DEFAULT) for key, value in kwargs.items()}).values())).cast("float32"),
prune=True,
)
test_value = None
for iteration in range(3):
GlobalCounters.reset()
print(f"run {iteration}")
with Context(DEBUG=max(DEBUG.value, 2 if iteration == 2 else 1), OPENPILOT_HACKS=1):
result = run_onnx_jit(**inputs).numpy()
if iteration == 1:
test_value = np.copy(result)
kernel_asts = {Ops.PROGRAM}
kernel_calls = [
node for node in run_onnx_jit.captured.linear.toposort(gate=lambda value: value.op not in kernel_asts)
if node.op is Ops.CALL and node.src[0].op in kernel_asts
]
print(f"captured {len(kernel_calls)} kernels")
np.testing.assert_equal(test_value, result, "JIT run failed")
print("jit run validated")
kernel_count = 0
read_image_count = 0
gated_read_image_count = 0
for call in kernel_calls:
_, _, source, _ = call.src[0].src
rendered = source.arg
kernel_count += 1
read_image_count += rendered.count("read_image")
gated_read_image_count += rendered.count("?read_image")
for value in (match.group(1) for match in re.finditer(r"(val\d+)\s*=\s*read_imagef\(", rendered)):
if re.search(fr"[?:]{value}\.[xyzw]", rendered):
gated_read_image_count += 1
print(f"{kernel_count=}, {read_image_count=}, {gated_read_image_count=}")
expected = {
"kernel count": (kernel_count, getenv("ALLOWED_KERNEL_COUNT", -1)),
"read image count": (read_image_count, getenv("ALLOWED_READ_IMAGE", -1)),
"gated read image count": (gated_read_image_count, getenv("ALLOWED_GATED_READ_IMAGE", -1)),
}
for name, (actual, allowed) in expected.items():
if allowed != -1:
assert actual == allowed, f"different {name}: {actual}, expected {allowed}"
with open(output, "wb") as handle:
pickle.dump(run_onnx_jit, handle)
print(f"model size is {os.path.getsize(onnx_file) / 1e6:.2f}M")
print(f"pkl size is {os.path.getsize(output) / 1e6:.2f}M")
return run_onnx_jit, inputs, test_value
def test_compiled(run, inputs, test_value):
step_times = []
for _ in range(20):
start = time.perf_counter()
output = run(**inputs)
queued = time.perf_counter()
value = output.numpy()
end = time.perf_counter()
step_times.append((end - start) * 1e3)
print(f"enqueue {(queued - start) * 1e3:6.2f} ms -- total run {step_times[-1]:6.2f} ms")
minimum = getenv("ASSERT_MIN_STEP_TIME", 0.0)
if minimum:
assert min(step_times) < minimum, f"expected minimum step time below {minimum} ms, got {min(step_times)} ms"
np.testing.assert_equal(test_value, value)
changed_inputs = {key: Tensor(item.numpy() * 2, device=item.device) for key, item in inputs.items()}
changed_value = run(**changed_inputs).numpy()
np.testing.assert_raises(AssertionError, np.testing.assert_array_equal, value, changed_value)
if __name__ == "__main__":
model_path = sys.argv[1]
output_path = sys.argv[2]
if stash := os.environ.get("IQPILOT_MODEL_STASH"):
stashed_model = os.path.join(stash, os.path.basename(output_path))
if os.path.isfile(stashed_model) and os.path.getsize(stashed_model) > 0:
os.makedirs(os.path.dirname(output_path) or ".", exist_ok=True)
shutil.copyfile(stashed_model, output_path)
print(f"restored device-compiled model: {output_path}")
sys.exit(0)
_, input_values, expected_value = compile_model(model_path, output_path)
with open(output_path, "rb") as compiled_file:
compiled_model = pickle.load(compiled_file)
test_compiled(compiled_model, input_values, expected_value)

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 iqpilot.common.file_chunker import read_file_chunked
from iqpilot.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).to(WARP_DEVICE).reshape(1, dst_w).expand(dst_h, dst_w).reshape(-1)
y_coords = Tensor.arange(dst_h).to(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 iqpilot.selfdrive.iqmodeld.metadata import build_metadata_record
from iqpilot.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,422 @@
#!/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)
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):
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)
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)
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)
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 iqpilot.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 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 iqpilot.common.file_chunker import read_file_chunked
from iqpilot.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 iqpilot.system.camerad.cameras.nv12_info import get_nv12_info
from 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,119 @@
#!/usr/bin/env python3
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
Compile the backend-neutral warp-only artifact: NV12 camera frames + 3x3
transforms -> (2, 6, model_h/2, model_w/2) uint8 warped tensor, on the device
GPU (QCOM). maciqmodeld runs this locally
and feed the output to their backend, so the big model's image pipeline is
bit-identical to comma's fused pkl warp stage.
Run ON the device (needs the QCOM backend):
cd /data/openpilot && DEV=QCOM WARP_DEV=QCOM IMAGE=1 FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 \
python3 iqpilot/selfdrive/iqmodeld/tools/compile_warp.py \
--camera-resolutions 1928x1208 --output /data/models/emac_warp.pkl
The artifact is then split per-resolution into Paths.model_root().
"""
from __future__ import annotations
import argparse
import hashlib
import os
import pickle
from functools import partial
import numpy as np
SELFTEST_SEED = 20260817
from iqpilot.selfdrive.iqmodeld.temporal_state import DEFAULT_FRAME_SKIP, MODEL_INPUT_SPEC
from iqpilot.selfdrive.iqmodeld.tools.compile_supercombo import (
NV12Frame, WARP_INPUTS, compile_jit, make_random_images, make_warp, make_warp_input_queues,
)
MODEL_SIZE = (MODEL_INPUT_SPEC["img"][0][3] * 2, MODEL_INPUT_SPEC["img"][0][2] * 2) # (512, 256)
def _parse_size(s: str) -> tuple[int, int]:
w, h = s.lower().split("x")
return int(w), int(h)
def compile_warp(cam_w: int, cam_h: int, out_path: str | None = None,
frame_skip: int = DEFAULT_FRAME_SKIP) -> str:
"""Compile the warp-only QCOM JIT for one camera resolution and write the pkl.
Returns the artifact path. Callable from the workers so a fresh device
self-provisions the warp instead of erroring — needs the QCOM backend."""
# the QCOM warp env must be set before tinygrad is imported here
os.environ.setdefault("DEV", "QCOM")
os.environ.setdefault("WARP_DEV", "QCOM")
os.environ.setdefault("IMAGE", "1")
os.environ.setdefault("FLOAT16", "1")
os.environ.setdefault("NOLOCALS", "1")
os.environ.setdefault("JIT_BATCH_SIZE", "0")
from tinygrad.engine.jit import TinyJit
from iqpilot.system.camerad.cameras.nv12_info import get_nv12_info
from iqpilot.system.hardware.hw import Paths
model_w, model_h = MODEL_SIZE
input_shapes = {name: shape for name, (shape, _) in MODEL_INPUT_SPEC.items()}
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=os.getenv("WARP_DEV"))
warp_jit = TinyJit(make_warp(nv12, model_w, model_h, frame_skip), prune=True)
make_warp_queues = partial(make_warp_input_queues, input_shapes, frame_skip)
compiled = compile_jit(warp_jit, make_random_warp_inputs, WARP_INPUTS, make_warp_queues)
# historical artifact name: already-provisioned devices keep their warp
out_path = out_path or os.path.join(Paths.model_root(), f"emac_warp_{cam_w}x{cam_h}_tinygrad.pkl")
os.makedirs(os.path.dirname(out_path), exist_ok=True)
tmp = out_path + ".part"
bundle = {(cam_w, cam_h): compiled, "frame_skip": frame_skip, "model_size": MODEL_SIZE}
bundle["selftest"] = selftest_digest(compiled, cam_w, cam_h, nv12.size)
with open(tmp, "wb") as f:
pickle.dump(bundle, f)
os.replace(tmp, out_path) # atomic: a reader never sees a half-written pkl
return out_path
def main() -> None:
p = argparse.ArgumentParser()
p.add_argument("--camera-resolutions", type=_parse_size, nargs="+", default=[(1928, 1208)])
p.add_argument("--output", default=None)
p.add_argument("--frame-skip", type=int, default=DEFAULT_FRAME_SKIP)
args = p.parse_args()
for cam_w, cam_h in args.camera_resolutions:
out = compile_warp(cam_w, cam_h, args.output, frame_skip=args.frame_skip)
print(f"saved warp JIT to {out} ({os.path.getsize(out) / 1e6:.2f} MB)")
if __name__ == "__main__":
main()
def selftest_inputs(cam_w: int, cam_h: int, nv12_size: int):
"""A fixed synthetic frame pair and pair of matrices. Deterministic so the
digest is reproducible on the device that compiled the artifact."""
rng = np.random.default_rng(SELFTEST_SEED)
frame = rng.integers(0, 256, nv12_size, dtype=np.uint8)
big_frame = rng.integers(0, 256, nv12_size, dtype=np.uint8)
tfm = np.array([[0.7, 0.02, 300.0], [0.01, 0.7, 240.0], [0.0, 0.0, 1.0]], dtype=np.float32)
big_tfm = np.array([[0.5, 0.01, 380.0], [0.02, 0.5, 300.0], [0.0, 0.0, 1.0]], dtype=np.float32)
return frame, big_frame, tfm, big_tfm
def selftest_digest(compiled, cam_w: int, cam_h: int, nv12_size: int) -> str:
"""Hash the warp's output for a fixed input.
A warp artifact pinned to one tinygrad can still unpickle under another and
then compute silently wrong, which reaches the model as a garbage image and
looks like a bad model rather than a stale artifact. A version string cannot
see that; running it can."""
from tinygrad.tensor import Tensor
frame, big_frame, tfm, big_tfm = selftest_inputs(cam_w, cam_h, nv12_size)
dev = os.getenv("WARP_DEV") or "QCOM"
out = compiled(tfm=Tensor(tfm, device="NPY").realize(),
big_tfm=Tensor(big_tfm, device="NPY").realize(),
frame=Tensor(frame, device=dev).realize(),
big_frame=Tensor(big_frame, device=dev).realize())
return hashlib.sha256(out.numpy().astype(np.uint8).tobytes()).hexdigest()

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 iqpilot.common.file_chunker import read_file_chunked
from iqpilot.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 iqpilot.selfdrive.iqmodeld.metadata import build_metadata_record
from iqpilot.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 iqpilot.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);