IQ.Pilot Release Commit @ bec7652

This commit is contained in:
IQ.Lvbs history cleanup
2026-08-22 23:42:41 -05:00
commit 58039e647c
4603 changed files with 1236178 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,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,730 @@
#!/usr/bin/env python3
from __future__ import annotations
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):
cloudlog.warning("iqmodeld init")
sentry.set_tag("daemon", PROCESS_NAME)
cloudlog.bind(daemon=PROCESS_NAME)
setproctitle(PROCESS_NAME)
config_realtime_process(7, 54)
cloudlog.warning("setting up CL context")
self._gpu = WarpContext()
cloudlog.warning("CL context ready; loading model")
self._runtime = NeuralEngineState(self._gpu)
self._meta_layout = select_meta_layout()
cloudlog.warning("models loaded, iqmodeld starting")
self._cameras = CameraIngress(self._gpu)
self._pub = PubMaster(["modelV2", "drivingModelData", "cameraOdometry", "iqDriveModelData", "iqPerfTrace"])
self._sub = SubMaster([
"deviceState", "carState", "roadCameraState", "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
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
iq_msg.iqDriveModelData.lateralEdgeBlock = self._desire_logic.lateral_edge_block
populate_odometry_message(
pose_msg,
outputs,
main_stamp.frame_id,
dropped_frames,
main_stamp.timestamp_eof,
live_calib_seen,
)
self._pub.send("modelV2", model_msg)
self._pub.send("drivingModelData", driving_msg)
self._pub.send("cameraOdometry", pose_msg)
self._pub.send("iqDriveModelData", iq_msg)
def serve(self) -> None:
tick = 0
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):
InferenceDaemon(demo=demo).serve()
__all__ = [
"PROCESS_NAME",
"IQP_NAV_MODEL_INFLUENCE_ENABLED",
"TurnDirection",
"CaptureStamp",
"ReplayLedger",
"NeuralEngineState",
"CameraIngress",
"CalibrationAtlas",
"FrameDropMeter",
"InferenceDaemon",
"main",
]
if __name__ == "__main__":
try:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--demo", action="store_true", help="Run iqmodeld in demo mode.")
args = parser.parse_args()
main(demo=args.demo)
except KeyboardInterrupt:
cloudlog.warning(f"child {PROCESS_NAME} got SIGINT")
except Exception:
sentry.capture_exception()
raise

View File

@@ -0,0 +1,62 @@
{
"displayName": "Default (CD210)",
"environment": "development",
"generation": 12,
"index": 56,
"internalName": "C210M",
"is20hz": true,
"minimumSelectorVersion": 14,
"models": [
{
"artifact": {
"downloadUri": {
"sha256": "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,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,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,311 @@
#!/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_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.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,222 @@
"""
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 active.models}
self.is_20hz_3d = False
self.is_20hz = active.is20hz
self.inputs = {}
self.parser_method_dict = {}
self._model_data = None # active spec for the current operation
self._parser = self._constants = None
def _active_spec(self):
spec = self._model_data
if spec is None:
raise ValueError("Model data is not available. Ensure the model is loaded correctly.")
return spec
# views proxied straight off the active artifact spec; kept out of the class
# body (served via __getattr__) so the read surface stays data-driven
_SPEC_VIEW = frozenset(("input_shapes", "output_slices"))
def __getattr__(self, name):
if name == "constants":
return self._constants
if name == "vision_input_names":
return list(self._active_spec().input_shapes)
if name in ModelRunner._SPEC_VIEW:
return getattr(self._active_spec(), name)
raise AttributeError(name)
def prepare_inputs(self, imgs_cl, numpy_inputs, frames):
"""Stage image + numpy inputs for inference; implemented per backend."""
raise NotImplementedError
def _run_model(self):
"""Execute inference over the staged inputs; implemented per backend."""
raise NotImplementedError
def run_model(self):
# parsing happens inside each backend's _run_model
return self._run_model()
def _slice_outputs(self, model_outputs):
"""Split the flat output vector into named views per the artifact's slice table."""
sliced = {}
for tag, span in self._active_spec().output_slices.items():
sliced[tag] = model_outputs[np.newaxis, span]
if SEND_RAW_PRED:
sliced["raw_pred"] = model_outputs.copy()
return sliced
# ---- runner selection (which backend to build for the active bundle) ----------
def _single_artifact_prefix(bundle, prefix: str) -> bool:
return len(bundle.models) == 1 and bundle.models[0].artifact.fileName.startswith(prefix)
def _is_fused_bundle(bundle) -> bool:
return _single_artifact_prefix(bundle, "driving_fused_")
def _is_supercombo_bundle(bundle) -> bool:
return _single_artifact_prefix(bundle, "driving_supercombo_")
def _is_split_bundle(bundle) -> bool:
present = {m.type.raw for m in bundle.models}
split_kinds = {ModelType.vision, ModelType.policy, ModelType.offPolicy, ModelType.onPolicy}
return not present.isdisjoint(split_kinds)
def get_model_runner() -> "ModelRunner":
"""Build the runner backend that fits the active bundle (supercombo / fused /
combined-split / split / single). Concrete runners are imported lazily so one
backend failing to load can't take down the others at import time."""
from iqpilot.selfdrive.iqmodeld.models.runners.tinygrad.tinygrad_runner import (TinygradRunner,
TinygradSplitRunner)
bundle = _fetch_bundle()
if not (bundle and bundle.models):
return TinygradRunner(ModelType.supercombo)
if _is_supercombo_bundle(bundle):
from 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(bundle.models[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,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,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,72 @@
"""
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,
_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,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,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,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);