forked from IQ.Lvbs/IQ.Pilot
IQ.Pilot Release Commit @ f2a861c
This commit is contained in:
@@ -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")
|
||||
@@ -29,6 +29,7 @@ _ACTIVE_BUNDLE_KEY = "ModelManager_ActiveBundle"
|
||||
_MODELS_CACHE_KEY = "ModelManager_ModelsCache"
|
||||
_RUNNER_CACHE_KEY = "ModelRunnerTypeCache"
|
||||
_DOWNLOAD_INDEX_KEY = "ModelManager_DownloadIndex"
|
||||
_PENDING_INDEX_KEY = "ModelManager_PendingIndex"
|
||||
_PENDING_MODEL_RESTORE_FILE = "/data/k3_pending_model_restore"
|
||||
_STOCK_RUNNER = int(Runner.stock)
|
||||
_TINYGRAD_RUNNER = int(Runner.tinygrad)
|
||||
@@ -40,7 +41,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 +85,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
|
||||
|
||||
@@ -227,6 +227,7 @@ def select_default_model(params: Params = None) -> None:
|
||||
bundle_dict = _load_default_bundle_dict()
|
||||
ensure_default_model_files(bundle_dict)
|
||||
params.remove(_DOWNLOAD_INDEX_KEY)
|
||||
params.remove(_PENDING_INDEX_KEY)
|
||||
params.put(_ACTIVE_BUNDLE_KEY, bundle_dict)
|
||||
params.remove(_RUNNER_CACHE_KEY)
|
||||
params.put(_RUNNER_CACHE_KEY, _TINYGRAD_RUNNER)
|
||||
@@ -239,10 +240,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
|
||||
@@ -114,7 +129,7 @@ class ModelRunner(RunnerRoot):
|
||||
if not active:
|
||||
raise ValueError("runner started without an active model bundle")
|
||||
|
||||
self.models = {spec.type.raw: ArtifactSpec(spec) for spec in active.models}
|
||||
self.models = {spec.type.raw: ArtifactSpec(spec) for spec in _qcom_models(active)}
|
||||
self.is_20hz_3d = False
|
||||
self.is_20hz = active.is20hz
|
||||
self.inputs = {}
|
||||
@@ -165,8 +180,15 @@ class ModelRunner(RunnerRoot):
|
||||
|
||||
# ---- runner selection (which backend to build for the active bundle) ----------
|
||||
|
||||
def _qcom_models(bundle) -> list:
|
||||
# usbeMac artifacts ride along in a bundle for the eGPU host; they are never
|
||||
# loaded on QCOM and must not affect runner classification
|
||||
return [m for m in bundle.models if m.type.raw != ModelType.usbeMac]
|
||||
|
||||
|
||||
def _single_artifact_prefix(bundle, prefix: str) -> bool:
|
||||
return len(bundle.models) == 1 and bundle.models[0].artifact.fileName.startswith(prefix)
|
||||
models = _qcom_models(bundle)
|
||||
return len(models) == 1 and models[0].artifact.fileName.startswith(prefix)
|
||||
|
||||
|
||||
def _is_fused_bundle(bundle) -> bool:
|
||||
@@ -178,7 +200,7 @@ def _is_supercombo_bundle(bundle) -> bool:
|
||||
|
||||
|
||||
def _is_split_bundle(bundle) -> bool:
|
||||
present = {m.type.raw for m in bundle.models}
|
||||
present = {m.type.raw for m in _qcom_models(bundle)}
|
||||
split_kinds = {ModelType.vision, ModelType.policy, ModelType.offPolicy, ModelType.onPolicy}
|
||||
return not present.isdisjoint(split_kinds)
|
||||
|
||||
@@ -187,21 +209,23 @@ 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):
|
||||
# an eMac-only bundle (no QCOM-loadable models) runs the stock default on
|
||||
# device; the big host serves the bundle's precompiled artifact
|
||||
if not (bundle and bundle.models and _qcom_models(bundle)):
|
||||
return TinygradRunner(ModelType.supercombo)
|
||||
|
||||
if _is_supercombo_bundle(bundle):
|
||||
from 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()
|
||||
return TinygradRunner(bundle.models[0].type.raw)
|
||||
return TinygradRunner(_qcom_models(bundle)[0].type.raw)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user