IQ.Pilot Release Commit @ bec7652

This commit is contained in:
IQ.Lvbs CI [bot]
2026-08-22 21:28:16 -05:00
parent 9e52535231
commit e0fd0efe96
4825 changed files with 177522 additions and 75780 deletions

View File

@@ -4,7 +4,7 @@ Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed
import glob
import os
Import("env", "envCython", "arch", "cereal", "messaging", "common", "visionipc")
Import("env", "envCython", "arch", "cereal", "messaging", "common", "visionipc", "tinygrad_dir")
lenv = env.Clone()
lenvCython = envCython.Clone()
@@ -23,9 +23,7 @@ def _inject_path_define(symbol, filename):
def _tinygrad_sources():
root = env.Dir("#tinygrad_repo").relpath
workspace = env.Dir("#").abspath
return ["#" + path for path in glob.glob(root + "/**", recursive=True, root_dir=workspace) if "pycache" not in path]
return [path for path in glob.glob(tinygrad_dir + "/**", recursive=True) if "pycache" not in path]
def _present_models():
@@ -84,13 +82,12 @@ _queue_metadata_generation(present_models, tinygrad_files)
def tg_compile(flags, model_name):
pythonpath_string = 'PYTHONPATH="${PYTHONPATH}:' + env.Dir("#tinygrad_repo").abspath + '"'
fn = File(f"models/{model_name}").abspath
return lenv.Command(
fn + "_tinygrad.pkl",
[fn + ".onnx"] + tinygrad_files,
lenv.PrettyAction(
f'${{PYWARN}} {pythonpath_string} {flags} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {fn}_tinygrad.pkl',
f'${{PYWARN}} {flags} python3 {Dir("#iqpilot/selfdrive/iqmodeld/tools").abspath}/compile_model.py {fn}.onnx {fn}_tinygrad.pkl',
'MODEL', logfile='${TARGET}.log')
)
@@ -98,22 +95,21 @@ def tg_compile(flags, model_name):
for model_name in present_models:
tg_compile(_tinygrad_flags(), model_name)
from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye
from openpilot.common.transformations.model import MEDMODEL_INPUT_SIZE
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):
pythonpath_string = 'PYTHONPATH="${PYTHONPATH}:' + env.Dir("#tinygrad_repo").abspath + '"'
model_dir = Dir("models").abspath
model_w, model_h = MEDMODEL_INPUT_SIZE
camera_args = " ".join(f"{cw}x{ch}" for cw, ch in FUSED_CAMERA_CONFIGS)
out_pkl = File(f"models/{file_prefix}driving_fused_tinygrad.pkl").abspath
onnx_deps = [File(f"models/{file_prefix}{model_name}.onnx") for model_name in FUSED_TRIPLET]
cmd = (
f'${{PYWARN}} {pythonpath_string} {flags} python3 {Dir("#iqpilot/selfdrive/iqmodeld/tools").abspath}/compile_daemon.py '
f'${{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 '

View File

@@ -3,7 +3,7 @@ Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed
"""
import numpy as np
from openpilot.common.transformations.camera import DEVICE_CAMERAS
from iqpilot.common.transformations.camera import DEVICE_CAMERAS
MAX_CAMERA_OFFSET_METERS = 0.35
@@ -31,7 +31,9 @@ def _camera_profile(sm):
def _calibration_height(sm) -> float:
return sm["liveCalibration"].height[0] if sm["liveCalibration"].height else 1.22
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):

View File

@@ -6,9 +6,12 @@ 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]:
peak_index = steps - 1
return [limit * ((index / peak_index) ** 2) for index in range(steps)]
return [index_function(index, max_val=limit, max_idx=steps - 1) for index in range(steps)]
def _probability_window(*values: float) -> np.ndarray:

View File

@@ -5,56 +5,57 @@ import time
from dataclasses import dataclass
from typing import Any
import cereal.messaging as messaging
import iqpilot.cereal.messaging as messaging
import numpy as np
from cereal import car, custom, log
from cereal.messaging import PubMaster, SubMaster
from msgq.visionipc import VisionBuf, VisionIpcClient, VisionStreamType
from 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 openpilot.common.filter_simple import FirstOrderFilter
from openpilot.common.iq_perf import PerfSample, PerfTraceEmitter, PerfTraceRing
from openpilot.common.params import Params
from openpilot.common.realtime import DT_MDL, config_realtime_process
from openpilot.common.swaglog import cloudlog
from openpilot.common.transformations.camera import DEVICE_CAMERAS
from openpilot.common.transformations.model import get_warp_matrix
from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper
from openpilot.selfdrive.controls.lib.drive_helpers import (
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 openpilot.selfdrive.locationd.calibration_helpers import get_calibrated_rpy
from openpilot.system import sentry
from iqpilot.selfdrive.locationd.calibration_helpers import get_calibrated_rpy
from iqpilot.system import sentry
from openpilot.iqpilot.common.steer_delay import resolve_steer_delay
from openpilot.iqpilot.selfdrive.iqmodeld.models.helpers import get_active_bundle
from openpilot.iqpilot.selfdrive.iqmodeld.models.inference_state import InferenceStateBase
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.model_runner import get_model_runner
from openpilot.iqpilot.selfdrive.iqmodeld.camera import CameraOffsetHelper
from openpilot.iqpilot.selfdrive.iqmodeld.config import Plan
from openpilot.iqpilot.selfdrive.iqmodeld.messaging import (
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 openpilot.iqpilot.selfdrive.iqmodeld.metadata import select_meta_layout
from iqpilot.selfdrive.iqmodeld.metadata import select_meta_layout
try:
from openpilot.iqpilot.selfdrive.iqmodeld.native.iqmodel_pyx import RoadProjector, WarpContext
from iqpilot.selfdrive.iqmodeld.native.iqmodel_pyx import RoadProjector, WarpContext
except ModuleNotFoundError:
class WarpContext:
def __init__(self, *args, **kwargs):
raise ModuleNotFoundError("openpilot.iqpilot.selfdrive.iqmodeld.native.iqmodel_pyx is not built")
raise ModuleNotFoundError("iqpilot.selfdrive.iqmodeld.native.iqmodel_pyx is not built")
class RoadProjector:
def __init__(self, *args, **kwargs):
raise ModuleNotFoundError("openpilot.iqpilot.selfdrive.iqmodeld.native.iqmodel_pyx is not built")
raise ModuleNotFoundError("iqpilot.selfdrive.iqmodeld.native.iqmodel_pyx is not built")
PROCESS_NAME = "iqpilot.selfdrive.iqmodeld.daemon"
@@ -62,6 +63,9 @@ 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:
@@ -422,12 +426,12 @@ class CalibrationAtlas:
self._offset_tuner.set_offset(offset_value)
def refresh(self, sm: SubMaster, main_is_wide: bool, dual_camera: bool) -> tuple[np.ndarray, np.ndarray, bool]:
if not (sm.seen["liveCalibration"] and sm.seen["roadCameraState"] and sm.seen["deviceState"]):
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["liveCalibration"])
rpy = get_calibrated_rpy(sm["extrinsicsCalibration"])
if rpy is None:
live_calib = sm["liveCalibration"]
live_calib = sm["extrinsicsCalibration"]
if len(live_calib.rpyCalib) == 3:
rpy = np.array(live_calib.rpyCalib, dtype=np.float32)
else:
@@ -484,8 +488,8 @@ class InferenceDaemon:
self._cameras = CameraIngress(self._gpu)
self._pub = PubMaster(["modelV2", "drivingModelData", "cameraOdometry", "iqDriveModelData", "iqPerfTrace"])
self._sub = SubMaster([
"deviceState", "carState", "roadCameraState", "liveCalibration",
"driverMonitoringState", "carControl", "liveDelay", "iqNavState", "radarState",
"deviceState", "carState", "roadCameraState", "extrinsicsCalibration",
"driverMonitoringState", "carControl", "lateralDelay", "iqNavState", "radarState",
])
self._message_memory = DrivePacketMemory()
self._params = Params()
@@ -509,7 +513,7 @@ class InferenceDaemon:
def _refresh_tunables(self, tick: int) -> None:
if tick % 60 != 0:
return
self._runtime.lat_delay = resolve_steer_delay(self._params, self._sub["liveDelay"].lateralDelay)
self._runtime.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))
@@ -586,6 +590,7 @@ class InferenceDaemon:
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,
@@ -603,12 +608,21 @@ class InferenceDaemon:
def serve(self) -> None:
tick = 0
starved_polls = 0
while True:
frame_pair = self._cameras.pull()
if frame_pair is None:
cloudlog.debug("visionipc frame missing")
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)

View File

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

@@ -6,10 +6,10 @@ from dataclasses import dataclass, field
import capnp
import numpy as np
from cereal import log
from openpilot.iqpilot.selfdrive.iqmodeld.models.helpers import plan_x_idxs_helper
from openpilot.iqpilot.selfdrive.iqmodeld.config import ModelConstants, Plan
from openpilot.selfdrive.controls.lib.drive_helpers import get_curvature_from_plan
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
@@ -67,7 +67,7 @@ def _assign_xyva(builder, t_points, x_track, y_track, v_track, a_track,
builder.aStd = a_std.tolist()
def _fit_path(builder, degree: int, x_track: np.ndarray, y_track: np.ndarray, z_track: np.ndarray) -> None:
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()
@@ -75,7 +75,7 @@ def _fit_path(builder, degree: int, x_track: np.ndarray, y_track: np.ndarray, z_
builder.zCoefficients = coeffs[:, 2].tolist()
def _lane_snapshot(builder, lane_lines, lane_probs: list[float]) -> None:
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]
@@ -123,7 +123,7 @@ def _write_plan_family(model_packet, driving_packet, outputs: dict[str, np.ndarr
_assign_xyz(model_packet.acceleration, ModelConstants.T_IDXS, *plan_rows[:, Plan.ACCELERATION].T)
_assign_xyz(model_packet.orientation, ModelConstants.T_IDXS, *plan_rows[:, Plan.T_FROM_CURRENT_EULER].T)
_assign_xyz(model_packet.orientationRate, ModelConstants.T_IDXS, *plan_rows[:, Plan.ORIENTATION_RATE].T)
_fit_path(driving_packet.path, ModelConstants.POLY_PATH_DEGREE, *plan_rows[:, Plan.POSITION].T)
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:
@@ -156,7 +156,7 @@ def _write_lane_family(model_packet, driving_packet, outputs: dict[str, np.ndarr
)
model_packet.laneLineStds = outputs["lane_lines_stds"][0, :, 0, 0].tolist()
model_packet.laneLineProbs = outputs["lane_lines_prob"][0, 1::2].tolist()
_lane_snapshot(driving_packet.laneLineMeta, model_packet.laneLines, model_packet.laneLineProbs)
fill_lane_line_meta(driving_packet.laneLineMeta, model_packet.laneLines, model_packet.laneLineProbs)
model_packet.init("roadEdges", 2)
for edge_idx in range(2):

View File

@@ -6,11 +6,11 @@ import sys
from collections.abc import Iterable
from typing import Any
from cereal import custom
from iqpilot.cereal import custom
from tinygrad.nn.onnx import OnnxPBParser
from openpilot.iqpilot.selfdrive.iqmodeld.models.helpers import get_active_bundle
from openpilot.iqpilot.selfdrive.iqmodeld.config import Meta
from iqpilot.selfdrive.iqmodeld.models.helpers import get_active_bundle
from iqpilot.selfdrive.iqmodeld.config import Meta
ModelBundle = custom.IQModelManager.ModelBundle

View File

@@ -1,3 +1,3 @@
"""
IQ model selection and runner support that is actively used by iqmodeld.
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""

View File

@@ -8,7 +8,7 @@ import os
import re
from pathlib import Path
from openpilot.system.hardware.hw import Paths
from iqpilot.system.hardware.hw import Paths
_MODEL_ROOT = Path(Paths.model_root())

View File

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

View File

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

View File

@@ -7,11 +7,11 @@ import os
import shutil
from pathlib import Path
from cereal import custom
from openpilot.common.params import Params
from openpilot.common.swaglog import cloudlog
from openpilot.iqpilot._proprietary_loader import ProprietaryModuleMissing, load_private_module
from openpilot.system.hardware.hw import Paths
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")
@@ -40,7 +40,6 @@ _DEFAULT_BUNDLE_REF = "default"
def get_default_model_bundle(_bundles):
"""Legacy compatibility hook: stock default is preinstalled, not a manifest bundle."""
return None
@@ -85,7 +84,7 @@ def _load_cached_manifest_bundles(params: Params):
continue
if "short_name" in raw_bundle:
from openpilot.iqpilot.selfdrive.iqmodeld.models.fetcher import ManifestDecoder
from iqpilot.selfdrive.iqmodeld.models.fetcher import ManifestDecoder
bundles.append(ManifestDecoder._decode_bundle(raw_bundle))
continue
@@ -239,10 +238,13 @@ def select_default_model(params: Params = None) -> None:
def seed_default_bundle_if_unset(params: Params = None) -> None:
params = Params() if params is None else params
if params.get(_ACTIVE_BUNDLE_KEY) or params.get(_DOWNLOAD_INDEX_KEY) is not None:
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}")

View File

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

View File

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

View File

@@ -1,3 +1,3 @@
"""
Runner interfaces used by iqmodeld model execution.
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""

View File

@@ -7,20 +7,21 @@ from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Any
import numpy as np
from cereal import custom
from openpilot.system.hardware import TICI
from openpilot.system.hardware.hw import Paths as _hw_paths
from openpilot.iqpilot.selfdrive.iqmodeld.models.helpers import get_active_bundle as _fetch_bundle
from openpilot.iqpilot.selfdrive.iqmodeld.models.combined_artifact import has_combined_split_artifact
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 openpilot.iqpilot.selfdrive.iqmodeld.native.iqmodel_pyx import GpuMemorySlot, RoadProjector
from iqpilot.selfdrive.iqmodeld.native.iqmodel_pyx import GpuMemorySlot, RoadProjector
else:
def _resolve_native_types() -> tuple[Any, Any]:
try:
from openpilot.iqpilot.selfdrive.iqmodeld.native.iqmodel_pyx import GpuMemorySlot as iq_clmem
from openpilot.iqpilot.selfdrive.iqmodeld.native.iqmodel_pyx import RoadProjector as iq_frame
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
@@ -59,11 +60,25 @@ def _configure_accelerator():
_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)."""
with open(os.path.join(CUSTOM_MODEL_PATH, metadata_filename), 'rb') as fh:
blob = _pk.load(fh)
return tuple(blob.get(field, {}) for field in _META_FIELDS)
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
@@ -187,20 +202,20 @@ def get_model_runner() -> "ModelRunner":
"""Build the runner backend that fits the active bundle (supercombo / fused /
combined-split / split / single). Concrete runners are imported lazily so one
backend failing to load can't take down the others at import time."""
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.tinygrad.tinygrad_runner import (TinygradRunner,
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 openpilot.iqpilot.selfdrive.iqmodeld.models.runners.tinygrad.supercombo_runner import TinygradSupercomboRunner
from iqpilot.selfdrive.iqmodeld.models.runners.tinygrad.supercombo_runner import TinygradSupercomboRunner
return TinygradSupercomboRunner()
if _is_fused_bundle(bundle):
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.tinygrad.fused_runner import TinygradFusedRunner
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 openpilot.iqpilot.selfdrive.iqmodeld.models.runners.tinygrad.combined_split_runner import TinygradCombinedSplitRunner
from iqpilot.selfdrive.iqmodeld.models.runners.tinygrad.combined_split_runner import TinygradCombinedSplitRunner
return TinygradCombinedSplitRunner()
if _is_split_bundle(bundle):
return TinygradSplitRunner()

View File

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

View File

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

View File

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

View File

@@ -11,12 +11,12 @@ from typing import Any
import numpy as np
from openpilot.iqpilot.selfdrive.iqmodeld.models.combined_artifact import resolve_combined_split_artifact
from openpilot.iqpilot.selfdrive.iqmodeld.models.helpers import get_active_bundle
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.model_runner import NumpyDict, ShapeDict, SliceDict
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.model_runner import ModelRunner
from openpilot.iqpilot.selfdrive.iqmodeld.models.split_model_constants import SplitModelConstants
from openpilot.iqpilot.selfdrive.iqmodeld.parser import PhaseParser
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():

View File

@@ -9,12 +9,12 @@ from typing import Any
import numpy as np
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.model_runner import (
from iqpilot.selfdrive.iqmodeld.models.runners.model_runner import (
CUSTOM_MODEL_PATH, NumpyDict, ShapeDict, SliceDict,
)
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.model_runner import ModelRunner
from openpilot.iqpilot.selfdrive.iqmodeld.models.split_model_constants import SplitModelConstants
from openpilot.iqpilot.selfdrive.iqmodeld.parser import PhaseParser
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():
@@ -27,8 +27,6 @@ WARP_DEV = os.getenv('WARP_DEV')
class TinygradFusedRunner(ModelRunner):
"""Runs a fused warp+vision+policy pkl. Bundle ships one `driving_fused_*` artifact."""
uses_opencl_warp: bool = False
def __init__(self):
@@ -110,19 +108,30 @@ class TinygradFusedRunner(ModelRunner):
'feat_q': zeros_f32((self._frame_skip * (fb[1] - 1) + 1, fb[0], fb[2])),
'desire_q': zeros_f32((self._frame_skip * dp[1], dp[0], dp[2])),
}
# shapes must match the captured run_policy JIT inputs
on_shapes = self._on_meta['input_shapes']
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(on_shapes['traffic_convention'], dtype=np.float32),
'action_t': np.zeros(on_shapes['action_t'], 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:
"""warp + vision + policy in one pass from raw NV12 bufs + transform matrices."""
Tensor, Device = _tinygrad_imports()
main_buf = bufs['img']
@@ -134,14 +143,13 @@ class TinygradFusedRunner(ModelRunner):
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:
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')
# frames go on the compute device to match the captured warp JIT
frame = self._frame_tensor('img', bufs['img'])
big_frame = self._frame_tensor('big_img', bufs['big_img'])
@@ -149,12 +157,13 @@ class TinygradFusedRunner(ModelRunner):
img, big_img = warp_jit(img_q=self._queues['img_q'], big_img_q=self._queues['big_img_q'],
tfm=npy('tfm'), big_tfm=npy('big_tfm'), frame=frame, big_frame=big_frame)
vision_out_t, on_out_t, off_out_t = self._run_policy(
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'), action_t=npy('action_t'))
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)
# parse each model's output on its own sliced dict; parsing a merged dict
# would run parse_dynamic_outputs twice and double-parse plan/lead
def _slice(tensor_out, meta) -> NumpyDict:
flat = tensor_out.numpy().flatten()
return {k: flat[np.newaxis, sl] for k, sl in meta['output_slices'].items() if k != 'pad'}

View File

@@ -9,9 +9,9 @@ from collections.abc import Callable
import numpy as np
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.model_runner import ModelType, NumpyDict
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.model_runner import RunnerRoot
from openpilot.iqpilot.selfdrive.iqmodeld.parser import ArchiveParser, PhaseParser
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):

View File

@@ -12,11 +12,11 @@ from typing import Any
import numpy as np
from openpilot.common.params import Params
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.model_runner import CUSTOM_MODEL_PATH, NumpyDict, ShapeDict, SliceDict
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.model_runner import ModelRunner
from openpilot.iqpilot.selfdrive.iqmodeld.models.split_model_constants import SplitModelConstants
from openpilot.iqpilot.selfdrive.iqmodeld.parser import PhaseParser
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():
@@ -68,8 +68,6 @@ def _is_jit_arg_mismatch(err: BaseException) -> bool:
class TinygradSupercomboRunner(ModelRunner):
"""Runs a single combined supercombo pkl. Bundle ships one `driving_supercombo_*` artifact."""
uses_opencl_warp: bool = False
def __init__(self):
@@ -282,7 +280,6 @@ class TinygradSupercomboRunner(ModelRunner):
zeros_u8 = lambda s: Tensor(np.zeros(s, dtype=np.uint8), device=Device.DEFAULT).contiguous().realize()
zeros_f32 = lambda s: Tensor(np.zeros(s, dtype=np.float32), device=Device.DEFAULT).contiguous().realize()
# packed npy block (single NPY tensor, mutated in place via views): order matches run_policy.split
shapes = {'desire': (dp[2],), 'traffic_convention': tuple(tc), 'action_t': tuple(at), 'prev_feat': (fb[0], fb[2])}
sizes = [math.prod(s) for s in shapes.values()]
packed = np.zeros(sum(sizes), dtype=np.float32)
@@ -318,7 +315,6 @@ class TinygradSupercomboRunner(ModelRunner):
self._npy['traffic_convention'][:] = numpy_inputs['traffic_convention']
if 'action_t' in numpy_inputs:
self._npy['action_t'][:] = numpy_inputs['action_t']
# self._npy['prev_feat'] holds last frame's hidden_state (zeros on the first frame)
frame = self._frame_tensor('img', bufs['img'])
big_frame = self._frame_tensor('big_img', bufs['big_img'])
@@ -334,11 +330,10 @@ class TinygradSupercomboRunner(ModelRunner):
raise
flat = out.numpy().flatten()
# feed hidden_state back as prev_feat for the next frame
self._npy['prev_feat'][:] = flat[self._hidden_slice].reshape(self._npy['prev_feat'].shape)
sliced = {k: flat[np.newaxis, sl] for k, sl in self._slices.items()}
return self._parser.parse_vision_outputs(sliced) # single-pass; parse_outputs double-parses a combined dict
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

@@ -8,9 +8,10 @@ import pickle
from dataclasses import dataclass
import numpy as np
from tinygrad.dtype import dtypes
from tinygrad.tensor import Tensor
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.model_runner import (
from iqpilot.selfdrive.iqmodeld.models.runners.model_runner import (
CLMemDict,
CUSTOM_MODEL_PATH,
FrameDict,
@@ -19,18 +20,18 @@ from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.model_runner import (
ShapeDict,
SliceDict,
)
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.model_runner import ModelRunner
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.tinygrad.model_types import (
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 openpilot.iqpilot.selfdrive.iqmodeld.models.split_model_constants import SplitModelConstants
from openpilot.iqpilot.selfdrive.iqmodeld.config import ModelConstants
from openpilot.iqpilot.selfdrive.iqmodeld.runtime.tinygrad import qcom_tensor_from_opencl_address
from openpilot.system.hardware import TICI
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)
@@ -84,6 +85,9 @@ class TinygradRunner(ModelRunner, SupercomboTinygrad, PolicyTinygrad, VisionTiny
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()}

View File

@@ -1,4 +1,3 @@
# openpilot model I/O constants (comma.ai, MIT — see LICENSE)
import numpy as np
@@ -7,7 +6,6 @@ def index_function(idx, max_val=192, max_idx=32):
class SplitModelConstants:
# time and distance indices
IDX_N = 33
T_IDXS = [index_function(idx, max_val=10.0) for idx in range(IDX_N)]
X_IDXS = [index_function(idx, max_val=192.0) for idx in range(IDX_N)]
@@ -15,7 +13,6 @@ class SplitModelConstants:
LEAD_T_OFFSETS = [0., 2., 4.]
META_T_IDXS = [2., 4., 6., 8., 10.]
# split-model temporal / history run parameters
MODEL_FREQ = 20
HISTORY_FREQ = 5
HISTORY_LEN_SECONDS = 5
@@ -31,7 +28,6 @@ class SplitModelConstants:
LATERAL_CONTROL_PARAMS_LEN = 2
PREV_DESIRED_CURV_LEN = 1
# model outputs constants
FCW_THRESHOLDS_5MS2 = np.array([.05, .05, .15, .15, .15], dtype=np.float32)
FCW_THRESHOLDS_3MS2 = np.array([.7, .7], dtype=np.float32)
FCW_5MS2_PROBS_WIDTH = 5
@@ -71,7 +67,6 @@ class SplitModelConstants:
POLY_PATH_DEGREE = 4
# model outputs slices
class Plan:
POSITION = slice(0, 3)
VELOCITY = slice(3, 6)
@@ -82,14 +77,12 @@ class Plan:
class Meta:
ENGAGED = slice(0, 1)
# next 2, 4, 6, 8, 10 seconds
GAS_DISENGAGE = slice(1, 31, 6)
BRAKE_DISENGAGE = slice(2, 31, 6)
STEER_OVERRIDE = slice(3, 31, 6)
HARD_BRAKE_3 = slice(4, 31, 6)
HARD_BRAKE_4 = slice(5, 31, 6)
HARD_BRAKE_5 = slice(6, 31, 6)
# next 0, 2, 4, 6, 8, 10 seconds
GAS_PRESS = slice(31, 55, 4)
BRAKE_PRESS = slice(32, 55, 4)
LEFT_BLINKER = slice(33, 55, 4)

View File

@@ -4,24 +4,24 @@ from dataclasses import dataclass
import numpy as np
from openpilot.iqpilot.selfdrive.iqmodeld.models.split_model_constants import SplitModelConstants
from openpilot.iqpilot.selfdrive.iqmodeld.config import ModelConstants
from iqpilot.selfdrive.iqmodeld.models.split_model_constants import SplitModelConstants
from iqpilot.selfdrive.iqmodeld.config import ModelConstants
def _bounded_exp(values, out=None):
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 + _bounded_exp(-values))
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):
_bounded_exp(values, out=values)
safe_exp(values, out=values)
else:
values = _bounded_exp(values)
values = safe_exp(values)
values /= np.sum(values, axis=axis, keepdims=True)
return values
@@ -56,7 +56,7 @@ class _TensorKitchen:
raw = self._grab(outputs, tensor_name)
if raw is None:
return
outputs[tensor_name] = _sigmoid(raw)
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)
@@ -66,7 +66,7 @@ class _TensorKitchen:
reshaped = raw.reshape((raw.shape[0], max(recipe.input_heads, 1), -1))
value_count = (reshaped.shape[2] - recipe.output_heads) // 2
means = reshaped[:, :, :value_count]
stds = _bounded_exp(reshaped[:, :, value_count:2 * value_count])
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)

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

@@ -1,5 +1,4 @@
// Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
// clang++ -O2 repro.cc && ./a.out
#include <sys/types.h>
#include <unistd.h>

View File

@@ -1,3 +1,6 @@
"""
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
@@ -5,12 +8,12 @@ from types import SimpleNamespace
import numpy as np
import pytest
from cereal import log
from iqpilot.cereal import log
from openpilot.iqpilot.selfdrive.iqmodeld.config import Plan
from openpilot.iqpilot.selfdrive.iqmodeld.daemon import NeuralEngineState, _merged_plan
import openpilot.iqpilot.selfdrive.iqmodeld.daemon as iqmodeld_daemon
from openpilot.selfdrive.controls.lib.drive_helpers import smooth_value
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):

View File

@@ -1,3 +1,6 @@
"""
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
@@ -5,13 +8,14 @@ from pathlib import Path
import numpy as np
from openpilot.iqpilot.selfdrive.iqmodeld.models.combined_artifact import resolve_combined_split_artifact
import openpilot.iqpilot.selfdrive.iqmodeld.models.runners.model_runner as runner_helpers
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.tinygrad.combined_split_runner import TinygradCombinedSplitRunner
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.tinygrad import combined_split_runner as combined_runner_mod
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.model_runner import ModelType
from openpilot.iqpilot.selfdrive.iqmodeld.parser import PhaseParser
from openpilot.iqpilot.selfdrive.iqmodeld.tests.test_iqmodeld_contracts import _phase_sample
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
@@ -77,7 +81,7 @@ def test_resolve_combined_split_artifact_prefers_override(tmp_path: Path, monkey
expected = tmp_path / "driving_combined_demo.pkl"
expected.write_bytes(b"iq")
monkeypatch.setattr("openpilot.iqpilot.selfdrive.iqmodeld.models.combined_artifact._MODEL_ROOT", tmp_path)
monkeypatch.setattr("iqpilot.selfdrive.iqmodeld.models.combined_artifact._MODEL_ROOT", tmp_path)
assert resolve_combined_split_artifact(bundle) == expected
@@ -89,7 +93,7 @@ def test_get_model_runner_prefers_combined_split_artifact(monkeypatch):
], generation=11)
marker = object()
monkeypatch.setattr(runner_helpers, "get_active_bundle", lambda: bundle)
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)
@@ -103,9 +107,9 @@ def test_get_model_runner_keeps_split_bundle_on_existing_runner_without_combined
], generation=12)
marker = object()
monkeypatch.setattr(runner_helpers, "get_active_bundle", lambda: bundle)
monkeypatch.setattr(runner_helpers, "_fetch_bundle", lambda: bundle)
monkeypatch.setattr(runner_helpers, "has_combined_split_artifact", lambda _: False)
monkeypatch.setattr(runner_helpers, "TinygradSplitRunner", lambda: marker)
monkeypatch.setattr(tinygrad_runner_mod, "TinygradSplitRunner", lambda: marker)
assert runner_helpers.get_model_runner() is marker

View File

@@ -1,12 +1,15 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
from __future__ import annotations
import numpy as np
from openpilot.iqpilot.selfdrive.iqmodeld.tools.compile_supercombo import (
from iqpilot.selfdrive.iqmodeld.tools.compile_supercombo import (
_captured_devices,
_captured_queue_depth,
_validate_pose_outputs,
)
from iqpilot.selfdrive.iqmodeld.models.runners.tinygrad.supercombo_runner import _captured_queue_depth
class _Captured:

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

@@ -1,20 +1,23 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
from __future__ import annotations
import copy
import cereal.messaging as messaging
import iqpilot.cereal.messaging as messaging
import numpy as np
from cereal import log
from iqpilot.cereal import log
from openpilot.iqpilot.selfdrive.iqmodeld.config import Meta, ModelConstants
from openpilot.iqpilot.selfdrive.iqmodeld.messaging import (
from iqpilot.selfdrive.iqmodeld.config import Meta, ModelConstants
from iqpilot.selfdrive.iqmodeld.messaging import (
DrivePacketMemory,
pick_curvature,
populate_drive_messages,
populate_odometry_message,
)
from openpilot.iqpilot.selfdrive.iqmodeld.models.split_model_constants import SplitModelConstants
from openpilot.iqpilot.selfdrive.iqmodeld.parser import ArchiveParser, PhaseParser
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]:

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

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

View File

@@ -1,17 +1,20 @@
"""
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
import pytest
from tinygrad.tensor import Tensor
import openpilot.iqpilot.selfdrive.iqmodeld.models.helpers as bundle_helpers
import openpilot.iqpilot.selfdrive.iqmodeld.models.runners.model_runner as model_runner_mod
import openpilot.iqpilot.selfdrive.iqmodeld.models.runners.tinygrad.tinygrad_runner as tinygrad_runner_mod
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.model_runner import ModelType
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.tinygrad.tinygrad_runner import TinygradRunner
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"
@@ -49,6 +52,7 @@ def _seed_runner_inputs(runner: TinygradRunner) -> None:
).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"),
@@ -56,7 +60,7 @@ def test_local_tinygrad_models_execute(monkeypatch):
])
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 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)

View File

@@ -1,5 +1,8 @@
from openpilot.iqpilot.selfdrive.iqmodeld import metadata, messaging, parser
from openpilot.iqpilot.selfdrive.iqmodeld.daemon import CaptureStamp, NeuralEngineState
"""
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():

View File

@@ -1,3 +1,6 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
from __future__ import annotations
import os
@@ -5,15 +8,14 @@ from dataclasses import dataclass
from pathlib import Path
import numpy as np
import pytest
from tinygrad.nn.onnx import OnnxRunner
from tinygrad.tensor import Tensor
import openpilot.iqpilot.selfdrive.iqmodeld.models.helpers as bundle_helpers
import openpilot.iqpilot.selfdrive.iqmodeld.models.runners.model_runner as model_runner_mod
import openpilot.iqpilot.selfdrive.iqmodeld.models.runners.tinygrad.tinygrad_runner as tinygrad_runner_mod
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.model_runner import ModelType
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.tinygrad.tinygrad_runner import TinygradRunner
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"))
@@ -86,6 +88,7 @@ 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)
@@ -122,8 +125,9 @@ def _run_onnx_bundle(bundle_dir: Path):
)
@pytest.mark.skipif(not SHARE_ROOT.is_dir(), reason="selector model share is not mounted")
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
@@ -133,8 +137,9 @@ def test_three_selector_models_parse_via_share_onnx():
assert policy_raw.size > 0
@pytest.mark.skipif(not SHARE_ROOT.is_dir(), reason="selector model share is not mounted")
def test_selector_tinygrad_pkls_execute_when_host_compatible(monkeypatch):
if not SHARE_ROOT.is_dir():
return
selector_dirs = _find_selector_dirs(limit=10)
attempted = 0
executed = 0
@@ -151,6 +156,10 @@ def test_selector_tinygrad_pkls_execute_when_host_compatible(monkeypatch):
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
@@ -159,4 +168,4 @@ def test_selector_tinygrad_pkls_execute_when_host_compatible(monkeypatch):
break
if executed == 0:
pytest.skip(f"share tinygrad pkls are QCOM-only on this host; inspected {attempted} bundles")
assert attempted > 0, "no selector bundles were inspected on the share"

View File

@@ -1,3 +1,6 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
from __future__ import annotations
import hashlib
@@ -5,10 +8,10 @@ from pathlib import Path
import pytest
from cereal import custom
from openpilot.iqpilot.selfdrive.iqmodeld.models import helpers as model_helpers
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.tinygrad import supercombo_runner as supercombo_runner_mod
from openpilot.iqpilot.selfdrive.iqmodeld.models.runners.tinygrad.supercombo_runner import (
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,
)
@@ -153,6 +156,31 @@ def test_select_default_model_clears_custom_download_state(tmp_path: Path, monke
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)"})()

View File

@@ -1,3 +1,6 @@
"""
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
@@ -6,9 +9,9 @@ from types import SimpleNamespace
import numpy as np
import pytest
import openpilot.iqpilot.selfdrive.iqmodeld.models.helpers as bundle_helpers
import openpilot.iqpilot.selfdrive.iqmodeld.models.runners.model_runner as runner_helpers
import openpilot.iqpilot.selfdrive.iqmodeld.daemon as iqmodeld_daemon
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

View File

@@ -9,8 +9,8 @@ import time
import numpy as np
import cereal.messaging as messaging
from openpilot.system.manager.process_config import managed_processes
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"))

View File

@@ -5,7 +5,7 @@ Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed
from __future__ import annotations
from openpilot.iqpilot.selfdrive.iqmodeld.tools.daemon_jit_compiler import main
from iqpilot.selfdrive.iqmodeld.tools.daemon_jit_compiler import main
if __name__ == "__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

@@ -62,8 +62,8 @@ WARP_DEVICE = os.getenv("WARP_DEV")
def _read_shared_copy(path: str) -> str:
from openpilot.common.file_chunker import read_file_chunked
from openpilot.system.hardware.hw import Paths
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))
@@ -127,8 +127,8 @@ def _project_pixels(src_flat, inverse_matrix, dst_shape, src_shape, stride_pad,
dst_w, dst_h = dst_shape
src_h, src_w = src_shape
x_coords = Tensor.arange(dst_w, device=WARP_DEVICE).reshape(1, dst_w).expand(dst_h, dst_w).reshape(-1)
y_coords = Tensor.arange(dst_h, device=WARP_DEVICE).reshape(dst_h, 1).expand(dst_h, dst_w).reshape(-1)
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]
@@ -352,8 +352,8 @@ def _arg_parser() -> argparse.ArgumentParser:
def main(argv: list[str] | None = None) -> int:
from openpilot.iqpilot.selfdrive.iqmodeld.metadata import build_metadata_record
from openpilot.system.camerad.cameras.nv12_info import get_nv12_info
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

View File

@@ -59,7 +59,6 @@ def warp_perspective_tinygrad(src_flat, M_inv, dst_shape, src_shape, stride_pad,
x = Tensor.arange(w_dst).reshape(1, w_dst).expand(h_dst, w_dst).reshape(-1)
y = Tensor.arange(h_dst).reshape(h_dst, 1).expand(h_dst, w_dst).reshape(-1)
# inline 3x3 matmul as elementwise to avoid reduce op (enables fusion with gather)
src_x = M_inv[0, 0] * x + M_inv[0, 1] * y + M_inv[0, 2]
src_y = M_inv[1, 0] * x + M_inv[1, 1] * y + M_inv[1, 2]
src_w = M_inv[2, 0] * x + M_inv[2, 1] * y + M_inv[2, 2]
@@ -100,9 +99,7 @@ def make_frame_prepare(nv12: NV12Frame, model_w, model_h):
stride_pad = stride - cam_w
def frame_prepare_tinygrad(input_frame, M_inv):
# UV_SCALE @ M_inv @ UV_SCALE_INV simplifies to elementwise scaling
M_inv_uv = M_inv * Tensor([[1.0, 1.0, 0.5], [1.0, 1.0, 0.5], [2.0, 2.0, 1.0]], device=WARP_DEV)
# deinterleave NV12 UV plane (UVUV... -> separate U, V)
uv = input_frame[uv_offset:uv_offset + uv_height * stride].reshape(uv_height, stride)
with Context(SPLIT_REDUCEOP=0):
y = warp_perspective_tinygrad(input_frame[:cam_h*stride],
@@ -142,7 +139,6 @@ def get_policy_npy_shapes(input_shapes):
tc = input_shapes['traffic_convention'] # (1, 2)
at = input_shapes['action_t'] # (1, 2)
fb = input_shapes['features_buffer'] # (1, 24, 512)
# TODO prev_feat shouldn't exist and be handled inside the JIT, but corrupt on QCOM for now
shapes = {'desire': (dp[2],), 'traffic_convention': tuple(tc), 'action_t': tuple(at), 'prev_feat': (fb[0], fb[2])}
return shapes, [math.prod(s) for s in shapes.values()]
@@ -155,7 +151,6 @@ def make_input_queues(input_shapes, frame_skip, device):
shapes, sizes = get_policy_npy_shapes(input_shapes)
packed_npy_inputs = np.zeros(sum(sizes), dtype=np.float32)
# views into the packed inputs, to be refilled at runtime
npy.update({k: v.reshape(s) for (k, s), v in zip(shapes.items(), np.split(packed_npy_inputs, np.cumsum(sizes[:-1])), strict=True)})
input_queues.update({
'feat_q': Tensor(np.zeros((frame_skip * fb[1], fb[0], fb[2]), dtype=np.float32), device=device).contiguous().realize(),
@@ -284,7 +279,7 @@ def _slice_outputs(model_outputs: np.ndarray, output_slices: dict[str, slice]) -
def _validate_pose_outputs(parsed_outputs: dict[str, np.ndarray]) -> None:
from openpilot.selfdrive.locationd.locationd import MIN_STD_SANITY_CHECK, ROTATION_SANITY_CHECK, TRANS_SANITY_CHECK
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',
@@ -326,7 +321,7 @@ def _validate_pose_outputs(parsed_outputs: dict[str, np.ndarray]) -> None:
def validate_supercombo_release(run_policy_jit, model_runner, model_metadata, frame_skip, expected_device: str) -> None:
from openpilot.iqpilot.selfdrive.iqmodeld.parser import PhaseParser
from iqpilot.selfdrive.iqmodeld.parser import PhaseParser
direct_fn = make_run_policy(model_runner, model_metadata, frame_skip)
parser = PhaseParser()
@@ -370,8 +365,8 @@ def _parse_size(s):
def read_file_chunked_to_shm(path):
from openpilot.common.file_chunker import read_file_chunked
from openpilot.system.hardware.hw import Paths
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
@@ -381,8 +376,8 @@ def read_file_chunked_to_shm(path):
if __name__ == "__main__":
from tinygrad.nn.onnx import OnnxRunner
from openpilot.system.camerad.cameras.nv12_info import get_nv12_info
from openpilot.iqpilot.selfdrive.iqmodeld.metadata import build_metadata_record
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,

View File

@@ -272,8 +272,8 @@ def _parse_size(text: str) -> tuple[int, int]:
def _read_file_to_shared_memory(path: str) -> str:
from openpilot.common.file_chunker import read_file_chunked
from openpilot.system.hardware.hw import Paths
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))
@@ -295,8 +295,8 @@ def _arg_parser() -> argparse.ArgumentParser:
def main(argv: list[str] | None = None) -> int:
from openpilot.iqpilot.selfdrive.iqmodeld.metadata import build_metadata_record
from openpilot.system.camerad.cameras.nv12_info import get_nv12_info
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

View File

@@ -13,7 +13,7 @@ from pathlib import Path
import onnx
from openpilot.system.hardware.hw import Paths
from iqpilot.system.hardware.hw import Paths
_MODEL_STEMS = ("driving_off_policy", "driving_on_policy", "driving_policy", "driving_vision")