IQ.Pilot Release Commit @ bec7652
This commit is contained in:
@@ -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 '
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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/
|
||||
"""
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
@@ -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}")
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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()
|
||||
@@ -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/
|
||||
"""
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
"""
|
||||
ONNX runner support for iqmodeld.
|
||||
"""
|
||||
@@ -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)
|
||||
@@ -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/
|
||||
"""
|
||||
@@ -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():
|
||||
|
||||
@@ -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'}
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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()")
|
||||
|
||||
@@ -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()}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
|
||||
"""
|
||||
@@ -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>
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
128
iqpilot/selfdrive/iqmodeld/tests/test_fused_runner_guards.py
Normal file
128
iqpilot/selfdrive/iqmodeld/tests/test_fused_runner_guards.py
Normal 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
|
||||
@@ -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]:
|
||||
|
||||
72
iqpilot/selfdrive/iqmodeld/tests/test_lat_delay_source.py
Normal file
72
iqpilot/selfdrive/iqmodeld/tests/test_lat_delay_source.py
Normal 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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)"})()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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__":
|
||||
|
||||
125
iqpilot/selfdrive/iqmodeld/tools/compile_model.py
Normal file
125
iqpilot/selfdrive/iqmodeld/tools/compile_model.py
Normal 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)
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user