diff --git a/artifacts/iqpilot_emac_private/manifest.json b/artifacts/iqpilot_emac_private/manifest.json index 6bdc7e3a2..cdecac46a 100644 --- a/artifacts/iqpilot_emac_private/manifest.json +++ b/artifacts/iqpilot_emac_private/manifest.json @@ -196,8 +196,8 @@ }, "python/_iqclosure/iqpilot/selfdrive/iqmodeld/egpu_helpers.py": { "mode": 420, - "sha256": "4df0ac0a3f720ee01a70f82908e582682e84848023592a72bd45f2ca74c9b161", - "size": 5535 + "sha256": "0b8f118dc45ad0c3809855800960b47c893ee121ea75d94e8569dcce46d58ce5", + "size": 7312 }, "python/_iqclosure/iqpilot/selfdrive/iqmodeld/egpu_model.py": { "mode": 420, @@ -221,8 +221,8 @@ }, "python/_iqclosure/iqpilot/selfdrive/iqmodeld/model_bundle_downloader.py": { "mode": 420, - "sha256": "62c2c2830c7c8819bf42eb54a34db635128a31fbc2bbb71bc95415e5b873a3c0", - "size": 3189 + "sha256": "27aade5852507baf84ed460b9446d0de4df58a4a535b82c301113040b7ba472a", + "size": 7305 }, "python/_iqclosure/iqpilot/selfdrive/iqmodeld/model_channel.py": { "mode": 420, @@ -391,7 +391,7 @@ }, "python/_iqclosure/iqpilot/selfdrive/selfdrived/events.py": { "mode": 420, - "sha256": "e298175d13a99b55e183954d82346fa7adab6b6c161fc72b789f12601d1a9b43", + "sha256": "5e37693eeced1e3ba81f03234b7b0dad33b7564826d232d288998be5fae7efd6", "size": 37038 }, "python/_iqclosure/iqpilot/selfdrive/state_estimation/__init__.py": { @@ -531,22 +531,22 @@ }, "python/iqpilot_private/emac/bulk_transport.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "3f55e4c621ea86c0a6ac9ecac091fa90491048df5fc5bf09cbb668f0c0b71051", + "sha256": "cc7a4087336116187655ea52bc48391e447b81529c2b923e60bf886bc3c7f58a", "size": 334016 }, "python/iqpilot_private/emac/mac_client.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "e748eba2eec567e5f0c9e1dce60ef71795d2a99f58238ad89495dcee7672c70c", + "sha256": "9d48157df0d78be709cf3722743b6137cbf73eb53c315d7307e6a214ae140664", "size": 267952 }, "python/iqpilot_private/emac/mac_protocol.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "46f79e0fe9d799003b49e5fe11969a1716974a9c6d20d52488d79ef8b7137a5a", + "sha256": "9b7dc1997e780198c2532045e41236dd342b4dc4d3cd31b06be27246121d6ab7", "size": 203544 }, "python/iqpilot_private/emac/maciqmodeld.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "b7acc1edcf50dd2e9c2d88f2f4824d0a28b0def3a57b144a340ddca1bdd14254", + "sha256": "ec1f1f0ce541abf00021757fa25c4de54c0037d36eb0d194bdf4ad6c77c778e6", "size": 333336 }, "runtime": { @@ -558,9 +558,9 @@ } }, "signatures": { - "python/iqpilot_private/emac/bulk_transport.cpython-312-aarch64-linux-gnu.so": "zdchbAhGhL0eeX6qgQUw0CwrPGIg896Sv/0AG0hHrJ/1Ek7KnB4llxmWUA/e4HB4hlCqTEMO416N3csIfDikBA==", - "python/iqpilot_private/emac/mac_client.cpython-312-aarch64-linux-gnu.so": "p0ukaERkDTTF6vVVn0TK1ImrPMArUWmqAY2kJnctlb38486SIeFZxC4gCP0cU9++j7i7ydmXNOcUqxG7qIPECQ==", - "python/iqpilot_private/emac/mac_protocol.cpython-312-aarch64-linux-gnu.so": "5rLpvBoiOP+/N/zblRnXcltpOtULO3uZx2bQYeCM/A8x8m9HLhIBUMJzSVIYj5VcbvgHmzc911UE18RrwEmyCg==", - "python/iqpilot_private/emac/maciqmodeld.cpython-312-aarch64-linux-gnu.so": "otKpewDX8CPqirHPXn+mTht7oi/AgkQiZ2VXPZM3/wryLSvzRKJzMyf5nXZ/lb0Z8J8vsFKUkdVhxKgYkeLIDw==" + "python/iqpilot_private/emac/bulk_transport.cpython-312-aarch64-linux-gnu.so": "hclRnmzBp4zVtU1CKeTqxG59yYTRHAGcwAbHgKgmw1/82SuqvyP4MbU0pFG9UpwtskIdpVsppHqriZjqGb5rAg==", + "python/iqpilot_private/emac/mac_client.cpython-312-aarch64-linux-gnu.so": "wQoF1yRiJxfo3NMOJ4PC2RDRRkfromNc2n1JpHyizsSK12Sf16Sjy1+yf2muujcs2n/gWjZbhozrCFgD++12Bg==", + "python/iqpilot_private/emac/mac_protocol.cpython-312-aarch64-linux-gnu.so": "kQT6AOQlvYTbVu+B6FzjCdmNMbZj37WEvBdDKNCaHtJuyYmgkrFxmhCjm1KNaNlHQNXQjO8MyI8aYYNocAKGDQ==", + "python/iqpilot_private/emac/maciqmodeld.cpython-312-aarch64-linux-gnu.so": "MJXrXlkDjgdUeNxck8kh4SeLCBRLUaalY3vJ8nOAOGFCwGFE1UXrw+wBl6HaBNu86v0MlBrT1cN89GkNtdZUDg==" } } diff --git a/artifacts/iqpilot_emac_private/manifest.json.sig b/artifacts/iqpilot_emac_private/manifest.json.sig index 01fe5bbaa..666f8ba16 100644 --- a/artifacts/iqpilot_emac_private/manifest.json.sig +++ b/artifacts/iqpilot_emac_private/manifest.json.sig @@ -1 +1 @@ -9uZxIFB8JGkbVFGkwQJ+7gxvqL+EJf7PS0DqSsFYuiaw39lYS4qp5rzPOmn0ixSDSYlzsEwnwq5ojMdbX/ZJCQ== +ViYlJji0FjCcbz07ouwB45Lnj1xSE5DytMonAUAg0KskmG9gyid8fEGvrHNZxQDPRD4EpL0N9oIasaFTKFWcDA== diff --git a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/egpu_helpers.py b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/egpu_helpers.py index 9e953fb9a..1317d78be 100644 --- a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/egpu_helpers.py +++ b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/egpu_helpers.py @@ -6,12 +6,16 @@ from __future__ import annotations import hashlib import json import os + +from iqpilot.common.swaglog import cloudlog import urllib.request from pathlib import Path from iqpilot.system.hardware.usb import egpu_dock_ready USB_SYSFS_ROOT = "/sys/bus/usb/devices" +FIRMWARE_MIRROR = os.getenv("IQ_EGPU_FIRMWARE_MIRROR", "/data/firmware/tinygrad") +TINYGRAD_CACHE = "/data/.cache" COMMA_LFS_BATCH_URL = "https://gitlab.com/commaai/openpilot-lfs.git/info/lfs/objects/batch" @@ -62,6 +66,11 @@ def egpu_policy_pkl_path(meta: dict) -> str: return os.path.join(Paths.model_root(), f"egpu_{meta['key']}_{meta['sha256'][:8]}_amd_policy.pkl") +def egpu_oob_pkl_path(meta: dict) -> str: + from iqpilot.system.hardware.hw import Paths + return os.path.join(Paths.model_root(), f"egpu_{meta['key']}_{meta['sha256'][:8]}_amd_policy_oob.pkl") + + def onnx_cache_path(meta: dict) -> str: from iqpilot.system.hardware.hw import Paths return os.path.join(Paths.model_root(), f"{meta['model_name']}_{meta['sha256'][:8]}.onnx") @@ -119,9 +128,14 @@ def download_onnx(meta: dict, progress_cb=None) -> str: if not download_url: raise RuntimeError(f"model {meta['key']} has no download source; stage the onnx at {onnx_cache_path(meta)}") - url = resolve_download_url(download_url, meta["sha256"], size) path = onnx_cache_path(meta) os.makedirs(os.path.dirname(path), exist_ok=True) + try: + from iqpilot.selfdrive.iqmodeld.model_bundle_downloader import download_hf_file + return download_hf_file(f"onnx/{meta['sha256']}.onnx", path, meta["sha256"], int(size or 0), progress_cb=progress_cb) + except Exception as e: + cloudlog.warning(f"onnx {meta['key']} unavailable from HF ({e}); falling back to {download_url.split(':', 1)[0]}") + url = resolve_download_url(download_url, meta["sha256"], size) tmp = path + ".part" digest = hashlib.sha256() got = 0 @@ -142,12 +156,20 @@ def download_onnx(meta: dict, progress_cb=None) -> str: return path -def download_precompiled(meta: dict, progress_cb=None, policy: bool = False) -> str | None: - art = meta.get("egpu_policy_artifact" if policy else "egpu_artifact") - if not art or not art.get("objects"): +def download_precompiled(meta: dict, progress_cb=None, policy: bool = False, oob: bool = False) -> str | None: + field = "egpu_oob_artifact" if oob else "egpu_policy_artifact" if policy else "egpu_artifact" + art = meta.get(field) + if not art or not (art.get("objects") or art.get("hf_path")): return None - from iqpilot.selfdrive.iqmodeld.model_bundle_downloader import download_lfs_bundle - dest = egpu_policy_pkl_path(meta) if policy else egpu_pkl_path(meta) + from iqpilot.selfdrive.iqmodeld.model_bundle_downloader import download_hf_file, download_lfs_bundle + dest = egpu_oob_pkl_path(meta) if oob else egpu_policy_pkl_path(meta) if policy else egpu_pkl_path(meta) + if art.get("hf_path"): + try: + return download_hf_file(art["hf_path"], dest, art["sha256"], int(art.get("size", 0)), progress_cb=progress_cb) + except Exception as e: + cloudlog.warning(f"precompiled {meta['key']} unavailable from HF ({e}); trying LFS") + if not art.get("objects"): + raise return download_lfs_bundle(art["objects"], dest, art["sha256"], int(art.get("size", 0)), progress_cb=progress_cb) @@ -161,12 +183,27 @@ def patch_tinygrad_fetch_fw() -> None: _orig = helpers.fetch_fw def fetch_fw(path, name, sha256): + mirror = pathlib.Path(FIRMWARE_MIRROR) / path / name + if mirror.is_file(): + blob = mirror.read_bytes() + if hashlib.sha256(blob).hexdigest() == sha256: + return blob p = pathlib.Path(f"/lib/firmware/{path}/{name}.zst") if p.is_file(): blob = zstandard.ZstdDecompressor().stream_reader(p.read_bytes()).read() if hashlib.sha256(blob).hexdigest() == sha256: return blob - return _orig(path, name, sha256) + blob = _orig(path, name, sha256) + # The dock's GPU firmware otherwise lives only in tinygrad's per-user download cache, which is + # a network fetch the first time a new HOME sees it; onroad the car is usually offline. + try: + mirror.parent.mkdir(parents=True, exist_ok=True) + tmp = mirror.with_suffix(mirror.suffix + ".part") + tmp.write_bytes(blob) + os.replace(tmp, mirror) + except OSError: + pass + return blob fetch_fw._iq_patched = True helpers.fetch_fw = fetch_fw diff --git a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/model_bundle_downloader.py b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/model_bundle_downloader.py index 35ac6acaf..f71e29594 100644 --- a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/model_bundle_downloader.py +++ b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/model_bundle_downloader.py @@ -27,6 +27,63 @@ def _requests_auth(): return None +def _hf(): + import importlib + for mod in ("iqpilot_private.models.git_auth", "iqpilot.selfdrive.iqmodeld.models.git_auth"): + try: + m = importlib.import_module(mod) + return m.get_hf_headers(), m.hf_resolve_url + except Exception: + continue + return None, None + + +def download_hf_file(hf_path: str, dst: str, sha256: str, size: int, progress_cb=None) -> str: + import requests + headers, resolve = _hf() + if resolve is None: + raise RuntimeError("no HF credentials available") + url = resolve(hf_path) + os.makedirs(os.path.dirname(dst), exist_ok=True) + tmp = dst + ".hfpart" + last_error: Exception | None = None + for _attempt in range(STREAM_RETRIES): + try: + have = os.path.getsize(tmp) if os.path.isfile(tmp) else 0 + if size and have > size: + os.remove(tmp) + have = 0 + if not size or have < size: + req_headers = dict(headers) + if have: + req_headers["Range"] = f"bytes={have}-" + with requests.get(url, headers=req_headers, stream=True, timeout=HTTP_TIMEOUT_S, allow_redirects=True) as r: + r.raise_for_status() + if have and r.status_code != 206: + have = 0 + with open(tmp, "ab" if have else "wb") as f: + got = have + for chunk in r.iter_content(CHUNK): + f.write(chunk) + got += len(chunk) + if progress_cb is not None and size: + progress_cb(min(1.0, got / size)) + digest = hashlib.sha256() + with open(tmp, "rb") as f: + for chunk in iter(lambda: f.read(CHUNK), b""): + digest.update(chunk) + if size and os.path.getsize(tmp) != size: + raise RuntimeError(f"size mismatch: {os.path.getsize(tmp)}/{size} bytes") + if sha256 and digest.hexdigest() != sha256: + os.remove(tmp) + raise RuntimeError("sha256 mismatch") + os.replace(tmp, dst) + return dst + except Exception as e: + last_error = e + raise RuntimeError(f"HF download failed: {last_error}") + + def _lfs_endpoint(base_url: str) -> str: return base_url.split("/raw/", 1)[0] + ".git/info/lfs" @@ -47,41 +104,108 @@ def _resolve_oid(session, base_url: str, oid: str, size: int, auth): return action["href"], action.get("header", {}) +def _part_path(dst: str, oid: str) -> str: + return os.path.join(dst + ".parts", oid) + + +def _part_complete(path: str, oid: str, size: int) -> bool: + if not os.path.isfile(path) or os.path.getsize(path) != size: + return False + digest = hashlib.sha256() + with open(path, "rb") as f: + for chunk in iter(lambda: f.read(CHUNK), b""): + digest.update(chunk) + return digest.hexdigest() == oid + + +def _fetch_part(session, base_url: str, obj: dict, path: str, auth, progress) -> None: + size = int(obj["size"]) + have = os.path.getsize(path) if os.path.isfile(path) else 0 + if have > size: + os.remove(path) + have = 0 + href, headers = _resolve_oid(session, base_url, obj["oid"], size, auth) + obj_auth = None if headers.get("Authorization") else auth + # LFS parts are content-addressed (oid == sha256), so a half-written part can be resumed with a + # Range request and verified afterwards instead of being thrown away on every restart. + if have: + headers = {**headers, "Range": f"bytes={have}-"} + with session.get(href, headers=headers, stream=True, timeout=HTTP_TIMEOUT_S, auth=obj_auth) as r: + r.raise_for_status() + if have and r.status_code != 206: + have = 0 + with open(path, "ab" if have else "wb") as f: + for chunk in r.iter_content(CHUNK): + f.write(chunk) + progress(len(chunk)) + + def download_lfs_bundle(objects: list, dst: str, sha256: str, size: int, progress_cb=None) -> str: import requests auth = _requests_auth() session = requests.Session() - os.makedirs(os.path.dirname(dst), exist_ok=True) - tmp = dst + ".part" + os.makedirs(dst + ".parts", exist_ok=True) total = int(size) or sum(int(o["size"]) for o in objects) + done_bytes = sum(int(o["size"]) for o in objects if _part_complete(_part_path(dst, o["oid"]), o["oid"], int(o["size"]))) + got = [done_bytes] + + def progress(n: int) -> None: + got[0] += n + if progress_cb is not None and total: + progress_cb(min(1.0, got[0] / total)) + last_error: Exception | None = None for base_url in MODELS_BASE_URLS: - for attempt in range(STREAM_RETRIES): + for _attempt in range(STREAM_RETRIES): try: - digest = hashlib.sha256() - got = 0 - with open(tmp, "wb") as f: - for obj in objects: - href, headers = _resolve_oid(session, base_url, obj["oid"], int(obj["size"]), auth) - obj_auth = None if headers.get("Authorization") else auth - with session.get(href, headers=headers, stream=True, timeout=120, auth=obj_auth) as r: - r.raise_for_status() - for chunk in r.iter_content(CHUNK): - f.write(chunk) - digest.update(chunk) - got += len(chunk) - if progress_cb is not None and total: - progress_cb(min(1.0, got / total)) - if total and got != total: - raise RuntimeError(f"size mismatch: {got}/{total} bytes") - if sha256 and digest.hexdigest() != sha256: - raise RuntimeError("sha256 mismatch") - os.replace(tmp, dst) - return dst + for obj in objects: + path = _part_path(dst, obj["oid"]) + if _part_complete(path, obj["oid"], int(obj["size"])): + continue + got[0] = done_bytes + _fetch_part(session, base_url, obj, path, auth, progress) + if not _part_complete(path, obj["oid"], int(obj["size"])): + if os.path.getsize(path) >= int(obj["size"]): + os.remove(path) + raise RuntimeError(f"part {obj['oid'][:12]} incomplete or failed verification") + done_bytes += int(obj["size"]) + got[0] = done_bytes + break except Exception as e: last_error = e - try: - os.remove(tmp) - except OSError: - pass - raise RuntimeError(f"model bundle download failed: {last_error}") + else: + continue + break + else: + raise RuntimeError(f"model bundle download failed: {last_error}") + + tmp = dst + ".part" + digest = hashlib.sha256() + with open(tmp, "wb") as out: + for obj in objects: + with open(_part_path(dst, obj["oid"]), "rb") as f: + for chunk in iter(lambda: f.read(CHUNK), b""): + out.write(chunk) + digest.update(chunk) + if total and os.path.getsize(tmp) != total: + os.remove(tmp) + raise RuntimeError(f"size mismatch: {os.path.getsize(tmp) if os.path.exists(tmp) else 0}/{total} bytes") + if sha256 and digest.hexdigest() != sha256: + os.remove(tmp) + for obj in objects: + try: + os.remove(_part_path(dst, obj["oid"])) + except OSError: + pass + raise RuntimeError("sha256 mismatch") + os.replace(tmp, dst) + for obj in objects: + try: + os.remove(_part_path(dst, obj["oid"])) + except OSError: + pass + try: + os.rmdir(dst + ".parts") + except OSError: + pass + return dst diff --git a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/selfdrived/events.py b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/selfdrived/events.py index 60a37668e..f296a9744 100644 --- a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/selfdrived/events.py +++ b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/selfdrived/events.py @@ -375,7 +375,7 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { "Pay Attention", "", AlertStatus.normal, AlertSize.small, - Priority.LOW, VisualAlert.none, AudibleAlert.none, .1), + Priority.MID, VisualAlert.none, AudibleAlert.none, .1), }, EventName.promptDriverDistracted: { @@ -901,7 +901,7 @@ if HARDWARE.get_device_type() == 'mici': "Pay Attention", "", AlertStatus.normal, AlertSize.small, - Priority.LOW, VisualAlert.none, AudibleAlert.none, 2), + Priority.MID, VisualAlert.none, AudibleAlert.none, 2), }, EventName.promptDriverDistracted: { ET.PERMANENT: Alert( diff --git a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/bulk_transport.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/bulk_transport.cpython-312-aarch64-linux-gnu.so index 3e00ace4f..61d676510 100755 Binary files a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/bulk_transport.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/bulk_transport.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/mac_client.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/mac_client.cpython-312-aarch64-linux-gnu.so index 68453dfd0..cd29790cb 100755 Binary files a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/mac_client.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/mac_client.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/mac_protocol.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/mac_protocol.cpython-312-aarch64-linux-gnu.so index 62c03d406..a5c157008 100755 Binary files a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/mac_protocol.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/mac_protocol.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/maciqmodeld.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/maciqmodeld.cpython-312-aarch64-linux-gnu.so index 6b8089334..574b8dd24 100755 Binary files a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/maciqmodeld.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/maciqmodeld.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_model_selector_private/manifest.json b/artifacts/iqpilot_model_selector_private/manifest.json index 4c6203a9b..5a3918714 100644 --- a/artifacts/iqpilot_model_selector_private/manifest.json +++ b/artifacts/iqpilot_model_selector_private/manifest.json @@ -176,7 +176,7 @@ }, "python/iqpilot_private/models/__init__.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "c6ff7efa8f20decbd9ee75a2b11d6be6b2b0888b45d90dce65ac3a1615e3afc5", + "sha256": "aec72c78d58c37afb1b57bea4a29d322032fffdfa59deabdc85b4322b5ca3787", "size": 67616 }, "python/iqpilot_private/models/__init__.py": { @@ -186,37 +186,37 @@ }, "python/iqpilot_private/models/big_catalog.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "60bb1b539739e75249c3034ff505aeb9bf38d6fcf8d7f713d61395104bee0002", + "sha256": "30a25ebd3b37e2307c3a80ae0fea70b314ab71427d313ba5e2d540c4ba7658ed", "size": 133840 }, "python/iqpilot_private/models/egpu_model.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "7111179a66be1ac8bbcefcd2c6f836150e9e31b01d36244315203123f980beb8", + "sha256": "e8cb29388e31f636d19f916d5a1d32f38c38113f5fa882344d4effceb6e37d85", "size": 68112 }, "python/iqpilot_private/models/emac_model_meta.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "16bec7e29dc9b07b743d523735eb3adfd5b43b0ffa7cc89d189ee050065aa08d", + "sha256": "b7938d9521352d5c1fa6b0d7ff8f3f11062e33bcc0d9a72ffb0edfb525423b6e", "size": 68264 }, "python/iqpilot_private/models/fetcher.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "8eaf6bc52ad788587e3a9bfbd06246f68931c8a151772e37c895c396001e5243", + "sha256": "dad0c3c3a73338647da25dead17da0db6cebb24527bc9db381cd16753741f676", "size": 136200 }, "python/iqpilot_private/models/git_auth.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "c785e13bf86fcfdc5bf0d5e5acc6a49b764f800e4a5e87bb740cd187fdeb4584", - "size": 67864 + "sha256": "ce815034dd8bdadb79131e0b51013a9f9772c4421dfb96b62431d84125089ad2", + "size": 68056 }, "python/iqpilot_private/models/helpers.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "d98244f8f9b3b1e1f412802d93095a0a05dedf5c6a98c9cefbee6c27e999d341", + "sha256": "798843f52120210c19da5b6c98723ca15d782ea9cd445cc8f96009471990d618", "size": 137944 }, "python/iqpilot_private/models/manager.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "9df118ff8a45497e258ac4cf520db3c80f74b8cd22a41b54bba26d372bc6a8e5", + "sha256": "96be19ef4f59b87e44092c61f70676468617365923c4c8b91cf7e0b88cc62c05", "size": 340000 }, "runtime": { @@ -228,13 +228,13 @@ } }, "signatures": { - "python/iqpilot_private/models/__init__.cpython-312-aarch64-linux-gnu.so": "XBVN/BJBOnlVrPJ0pq/+rAkGwSDkJk3t29YpPaYsWCQPWD0JADTip4a8vj0GvCQgXdGwNsMJb2b7Cu6TeoOOAw==", - "python/iqpilot_private/models/big_catalog.cpython-312-aarch64-linux-gnu.so": "zhE70qcpwK9vSvqYp7fKuMptsV2JMMpmsnUeyFuA89mtFDKzUoJi+jxwOsvcH1b0qk6CuemH86l0/UpA0AbRAg==", - "python/iqpilot_private/models/egpu_model.cpython-312-aarch64-linux-gnu.so": "4JFNjPPV2i8xPXrWrwpkuOWPDRCD/m0+bVGgWO9TEmFLKbAu+RQslAHesptxfDAyomJbeRLBvXk1KVRaTzQ+CA==", - "python/iqpilot_private/models/emac_model_meta.cpython-312-aarch64-linux-gnu.so": "Q1luLZ5apcCJ3Im8Py3uP0D42VYXdsw5y48FAtofFrCQJlZ+LTPshjSVbu2GYQnxny9fFt4J6DT9zvMtLheqAA==", - "python/iqpilot_private/models/fetcher.cpython-312-aarch64-linux-gnu.so": "YPc7Ix5gW6YCQX2DrpL5bWu5RKMIOCJvaR/xDwTJmo3/dfkgvfAv7jG41rTxpXaRl8rM8ipUoPI8tlKGzucqDw==", - "python/iqpilot_private/models/git_auth.cpython-312-aarch64-linux-gnu.so": "R6WdFIgsLn4/8fq5qSU64ye7n+Ql9tr3Vhy4gYKIGKWY5GkXplkc8KMA1ciO7yy0n6Qw5HPCmGKrAbkQerrfBw==", - "python/iqpilot_private/models/helpers.cpython-312-aarch64-linux-gnu.so": "BoA4zydMAnpLyAFxzjwvggxB8Hzx+OJd3puAAckzi0UiiGhdiTGl9jexUcGpOolST3pPCRSoCXT0fVihW/OFDw==", - "python/iqpilot_private/models/manager.cpython-312-aarch64-linux-gnu.so": "8VccT47QBNee1ZcnKSsHchV10dOJP0q55FtabpSZr+azLP+hmWiClG3j9VqRe5ctAzuMa53m7eSRjXiTKCpwDw==" + "python/iqpilot_private/models/__init__.cpython-312-aarch64-linux-gnu.so": "CHPHmtjVAGTdtbWnwE5awrEKFJHs06YK4Gao9IKVaHZYXyj5WAc/d4t7hOKM3lm2/UfBaraTdVTdeSOij3dpCQ==", + "python/iqpilot_private/models/big_catalog.cpython-312-aarch64-linux-gnu.so": "Ekiv6tp3mDx+7Z4lNI+byVomzjShe93UkYQEOhYwl6kHlpxN06A3IqV8y5pwlJII4ZpE5yYEX9qp+VMXsdm9Aw==", + "python/iqpilot_private/models/egpu_model.cpython-312-aarch64-linux-gnu.so": "njYujmFBsfewILpjLI0nUN34rqzMNITattpewwxE4LuTcdkAgihQ3w/S/aYetSJzUtV/SH9mk26DhMciwn8KAw==", + "python/iqpilot_private/models/emac_model_meta.cpython-312-aarch64-linux-gnu.so": "zE2s6oQ3YqanaFG4RMNPydekiblRQVBk7QEJ5G30W2thGqzWt+1BW+75XdwaCigw//rINxKHbt3Neu5rub7PAw==", + "python/iqpilot_private/models/fetcher.cpython-312-aarch64-linux-gnu.so": "hBKd/smbmwEszsGjU7FP2RNVmrINBpU4NPZMGDjEHAF7Rx1WBF49+pH1s0k571RBqdtqN7nya2HCnKKO0tHUBw==", + "python/iqpilot_private/models/git_auth.cpython-312-aarch64-linux-gnu.so": "eKa9qfsfNNaSQpqggWSGw5YV/6cJk5iJcmhY+N/v3xdFvEMfCsIf+N3BhP0S3ZDO5T2CeE/I/Xc+1QbZh7oNDQ==", + "python/iqpilot_private/models/helpers.cpython-312-aarch64-linux-gnu.so": "k4BcKQAua+1YSC6P8jVEu2wBrI+WL8Bb5kKQbOnBYn/lupnrA96vMRYu4XVVDLGBoIqK8L7K/PYCYxu9Cx4RDA==", + "python/iqpilot_private/models/manager.cpython-312-aarch64-linux-gnu.so": "+53NsV73jkF/1x9Bzsw99oZRhVBtqlQTeXaPfHydTAssd2V3UMO+BWqagtGbVG5qIOcgjaNweNExGu+Abo7iDA==" } } diff --git a/artifacts/iqpilot_model_selector_private/manifest.json.sig b/artifacts/iqpilot_model_selector_private/manifest.json.sig index ca30f6a04..09517d18c 100644 --- a/artifacts/iqpilot_model_selector_private/manifest.json.sig +++ b/artifacts/iqpilot_model_selector_private/manifest.json.sig @@ -1 +1 @@ -LQYoKAbNbtkF/8GkU70iyR+NZQI0fmM7PWUQ2IphOB0P4nCRjgRCnkkE/g3KS+d4f/R60fgakG135GVCQgsaBw== +8t0c8OQ6AY5JlVXqGQkiF2rlj3kN3MIwGHVz4TOgaudkW7XegnRhUqngcpNo5C74wFDKNGWsvXOJ4WtIiIrfCg== diff --git a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/__init__.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/__init__.cpython-312-aarch64-linux-gnu.so index bb552bd23..147259b81 100755 Binary files a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/__init__.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/__init__.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/big_catalog.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/big_catalog.cpython-312-aarch64-linux-gnu.so index a2c7fe82e..a8cd85cc0 100755 Binary files a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/big_catalog.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/big_catalog.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/egpu_model.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/egpu_model.cpython-312-aarch64-linux-gnu.so index c4328a9b9..033ae101f 100755 Binary files a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/egpu_model.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/egpu_model.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/emac_model_meta.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/emac_model_meta.cpython-312-aarch64-linux-gnu.so index 2f8f1719c..24bc620b5 100755 Binary files a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/emac_model_meta.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/emac_model_meta.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/fetcher.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/fetcher.cpython-312-aarch64-linux-gnu.so index 0ef104463..450526348 100755 Binary files a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/fetcher.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/fetcher.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/git_auth.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/git_auth.cpython-312-aarch64-linux-gnu.so index c9a9fdc05..695c881f5 100755 Binary files a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/git_auth.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/git_auth.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/helpers.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/helpers.cpython-312-aarch64-linux-gnu.so index 7af37b0b7..fa3e70ce6 100755 Binary files a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/helpers.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/helpers.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/manager.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/manager.cpython-312-aarch64-linux-gnu.so index 0926516c7..057766128 100755 Binary files a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/manager.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/manager.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_navd_private/.source_sha b/artifacts/iqpilot_navd_private/.source_sha index c835b7dab..006ed0202 100644 --- a/artifacts/iqpilot_navd_private/.source_sha +++ b/artifacts/iqpilot_navd_private/.source_sha @@ -1 +1 @@ -44200007e6d1ccd1e6016db08abf3f54d733913f +7ef341203873309cd6087fbd6a0d355183f21472 diff --git a/artifacts/iqpilot_navd_private/manifest.json b/artifacts/iqpilot_navd_private/manifest.json index b9fa27a2b..42c68bf7e 100644 --- a/artifacts/iqpilot_navd_private/manifest.json +++ b/artifacts/iqpilot_navd_private/manifest.json @@ -101,8 +101,8 @@ }, "python/_iqclosure/iqpilot/selfdrive/car/cruise.py": { "mode": 420, - "sha256": "6596f1f27b73452f13f89241fb6a2cc11096904bf37c665eccf739430f9ec63a", - "size": 14710 + "sha256": "cb74245bc10dff8db84eeba299ab56915bb399f391f67c552bf53aa52e55841c", + "size": 16399 }, "python/_iqclosure/iqpilot/selfdrive/car/long_increments.py": { "mode": 420, @@ -186,7 +186,7 @@ }, "python/iqpilot_private/navd/__init__.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "070f9e626498045eaa2420b4155eea666173bf2b9177570bf858bda16c6db653", + "sha256": "ab83e566caaae8db57a64209742a9243ccfb332daab10577eef1dd11ff2f062e", "size": 67616 }, "python/iqpilot_private/navd/__init__.py": { @@ -194,104 +194,109 @@ "sha256": "3faeaa3363b2f50fa135ad4c1f2466853d684d562120425f77a58039837fd8fb", "size": 69 }, + "python/iqpilot_private/navd/alpr_matcher.cpython-312-aarch64-linux-gnu.so": { + "mode": 493, + "sha256": "4252e7d3de4db89dd6bff89b4c59fae3fb009b3f150b8bf2f04e912e5030b591", + "size": 269936 + }, "python/iqpilot_private/navd/amap_client.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "9e20c2f70422428de5109e0fa9c5c26ca09b6e684a86f519dac82e539b9d529e", + "sha256": "75a0f2dfc9b6b21298f97be0d43079e873333faaaeb904a251247cf9b1958469", "size": 202040 }, "python/iqpilot_private/navd/event_builder.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "187419474acc3b1ef8307331c0143f9b1a4375a3f7b031beb8fea4bee9ae6e8c", + "sha256": "30ffec950cdaa4aaf49c7e7c1d2a150c98b13941a31067969ec6beffb4b4cc6e", "size": 136960 }, "python/iqpilot_private/navd/helpers.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "eff4eb0b5f23cbb3af85da0e55a861e82a1d8d3f8be3a0d8c4c8137003637c7e", + "sha256": "2a2b1be142b042cf85d008b7bc7605a5fc89e8c80f9303ffd8e7f503092cadbf", "size": 135960 }, "python/iqpilot_private/navd/incident_feed.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "091227540881f177d29d53ccd09eacdde413f672bc15523966fff7d20c055e94", + "sha256": "387b407e49fe9eb3debf58de1e80d26e878a55af8d7d386c69fe690fabdfc278", "size": 135528 }, "python/iqpilot_private/navd/iqmapd.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "1bf7f2f0846d4f5c52b12a6388e427a8e477c0d8fd67f88e3fb2186aa8d6a826", + "sha256": "4ae88149ae54971836bb0e539d3e6d8c400045d32b2524396ffb2e9bf3cd377e", "size": 135632 }, "python/iqpilot_private/navd/long_decel.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "183f2af1da64ba01bf0735ed6d9fb20ced5900a465f2608fb4332d4f4fbb0ba9", + "sha256": "4eaa7376674fd8ed11661fe9587b8756cb0816dd7dcf9ee02a7122f4e16c4208", "size": 69752 }, "python/iqpilot_private/navd/mapbox_client.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "85b6a11d4bebe5b17792d3a2c1d4cf6577284ed3465bc078202bd1b50244478f", + "sha256": "bf259ccce678c8450d3a8646d641e3c1cee841ce62106964ddd8f0af76590922", "size": 68144 }, "python/iqpilot_private/navd/nav_cameras.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "f873227e7a089ca5b3e637f38815f962bf6612d1d89a3d98ad9c2f13303f80b2", - "size": 135600 + "sha256": "0af88978fcc2f04be1d9378eb0702f78d31321d845bf2f5b92efead4a6246b41", + "size": 203056 }, "python/iqpilot_private/navd/nav_turn_signals.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "e590e12c1bfda8537fb9333cb87f5f9877355cd491fd4fbee59c7055c98c05e0", + "sha256": "6dab10706556491be6b2481f805ee4d016f04d9faaddd8943d384851d14352b7", "size": 135560 }, "python/iqpilot_private/navd/navd.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "f4de1e41111d765e04b0578b777aa6898337df07a19ea3ccd7d84740f33bb10c", - "size": 797296 + "sha256": "4bc9df93ca01b1623ae080ba468916c1ac1d22eb30d0b922bc97cc8d8f4177de", + "size": 797328 }, "python/iqpilot_private/navd/navincidentd.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "3d1c7a8479deee9d21ac61e38eccf321a0761fa7a104d85d75e770126063c3c5", + "sha256": "213cd32635b223ca3c39ccb5b65b2bbfe050b5401ff8e1285a4fd2f288107df5", "size": 135344 }, "python/iqpilot_private/navd/navrenderd.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "2621b0ee58c5a7db170050c54acbcea92b1bcfd3c5fc07f5342fce16e8e84069", + "sha256": "87c4fae3eda6107363d54a32d200b6237ce02aa67db429895f68935476424bd0", "size": 135584 }, "python/iqpilot_private/navd/osm_cameras.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "6bea816d9ab4cfe8eeebaaa39c3eec8f0d2f2e80d83ef8b685eb8444332d2286", - "size": 136392 + "sha256": "236ff9dcb24f0599f8feab2fcfbf76754816ae3a8c4d9c7692d86c3579c6feca", + "size": 202384 }, "python/iqpilot_private/navd/reroute.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "21a0e0fe1ae366fae40755ddcee1b2d14dd36208d0cd4ea93f6d28a2946433c9", + "sha256": "5ef4fad4e686b94b258a4592733451358e0838f9cfe3fa0b2012cc87e4f550da", "size": 136216 }, "python/iqpilot_private/navd/road_incidents.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "007b5062fd915d01f0eb782cb190b47025671ac643a2cc92b7a7eb36ed9f3bd1", + "sha256": "9833ba14d5565cfd9be9442a090d90134eb18dd26e2664aaf403cd8fc4e55906", "size": 203136 }, "python/iqpilot_private/navd/route_manager.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "1a34010bdf5f58678f8fd198162de2d16d2dae0d1aef91b56ea88c54122080ef", + "sha256": "6dc7c0b3600eaaf4429f960c68df3b2f9f50b44c515f3b8395541fe367cdcafc", "size": 877864 }, "python/iqpilot_private/navd/runtime_common.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "7d4ed2bf8c46b2bdb8d9558035301835c45e521cd5ceaee2a04fafdb3d3688ed", + "sha256": "df427f9ecd65f070a14b058b1ce3b12a6ff95cee0df4fb0ecbb7d281f6f85f48", "size": 135760 }, "python/iqpilot_private/navd/traffic.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "289c5ef19885a3b032ea9183ed0f889c16f09050fe2ecf0d8e1e544f31e57283", + "sha256": "d1bcf0d4bdf3cf3d1877185b1ffb282ae2f837c181be16f5b2562db40033c5ad", "size": 69720 }, "python/iqpilot_private/navd/turn_desire.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "f3defee1e4aa36f6493cb74f2343a31e3b65dd48dac22517ea9f5c160b76a907", + "sha256": "aebe5ee4db658d6383afbf5e2298820f66fb3a3deadf557a7d1557a24bf3189a", "size": 69768 }, "python/iqpilot_private/navd/web_server.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "2f49a31d16f1952eda641c9634f7450343d2f8289b5e050a44a629778965e23c", + "sha256": "ea7ad6c47b9597cb620a21a9328c4086acf1e688df3f7e9215e0b80204c7c36c", "size": 201400 }, "runtime": { @@ -331,26 +336,27 @@ } }, "signatures": { - "python/iqpilot_private/navd/__init__.cpython-312-aarch64-linux-gnu.so": "F5zYUvVEwqzW7SDJKbt0AABXyKtgJ7gO5M/akVREPJC5xgD2PuKSlxhdSnNmb8Q9LyaTbR8x3PSvSXngub+nDQ==", - "python/iqpilot_private/navd/amap_client.cpython-312-aarch64-linux-gnu.so": "/DOq7cGdZbH9s+pn5ey4IXO/eO1ubKg093e91ME9NVXy/F3y8+RjwZMwfqEQQSVC4pe8623LSRV4Z/pCUJFyDA==", - "python/iqpilot_private/navd/event_builder.cpython-312-aarch64-linux-gnu.so": "09miAJIhesOTdcFV2lZBKXSca/q+kKVD6U9wkEShaOESznVs1uZhILBFu4PiKBsALiQBZ/C2s/uQuC1IIItaDw==", - "python/iqpilot_private/navd/helpers.cpython-312-aarch64-linux-gnu.so": "1yKbUBIkSRX4kH+r268peztYOIKVVlAeKhiNShf1Kjc6/ROB/B9vSgE+NLQrNhsMyGkpOEk8HA97JJ8QxndvDA==", - "python/iqpilot_private/navd/incident_feed.cpython-312-aarch64-linux-gnu.so": "NvjR3FoKI567nWq3++SzPQkmCDCaDSTDPfE0GjEHgSBd/SJariIANKwSJCoPhZ436NbsrAxPY9ZJF3zZTQkFBg==", - "python/iqpilot_private/navd/iqmapd.cpython-312-aarch64-linux-gnu.so": "2omO3UL4OfaZ0esBEl1a2x/j+yic0OPNFcolUA+8y+s8/h9IZMe2bUcCbrOai3l4Rpm9BKunwl4EPtj3KiItDg==", - "python/iqpilot_private/navd/long_decel.cpython-312-aarch64-linux-gnu.so": "bchPSpi1tnIEe4Kidfo3iadRIHx2EymC5cUT6OBWpLuuk+ND23IsRw1kvLXGpp99hMzXtOwcfmEL79kEjqe6CQ==", - "python/iqpilot_private/navd/mapbox_client.cpython-312-aarch64-linux-gnu.so": "lejZRjrSDLGyVhTYq78QbozsgQxldbqq8z8MkNTB4ZFM7DXup5vUlOu7YJbdrEMs6JWVQsmKUJ67Eq6Fi5UhCA==", - "python/iqpilot_private/navd/nav_cameras.cpython-312-aarch64-linux-gnu.so": "F+YDu+AaFvNnSgAmdRfK3/QUGTHPmARLHeWF/RFPqE8aKOFgqJu5dIKKvuW+8UDc2HG+SQ7xaBe/rqnIQrRHAg==", - "python/iqpilot_private/navd/nav_turn_signals.cpython-312-aarch64-linux-gnu.so": "hKjPTpXjUUVobswo8TNWWmLcVvNYd7ADwj5N9APHnoXpkEbKHmaODtGNo5JjpNSoYxVDBnjm0FiYD7gXAtXmBQ==", - "python/iqpilot_private/navd/navd.cpython-312-aarch64-linux-gnu.so": "Jbfvl7PfzijCl/bWoAf0IoHrHvgS2xAgMpXfCBfI1tLzPrXTc6LadxKBPlVJfoNH2WBt1LqnIsjBlLcU0ysyCA==", - "python/iqpilot_private/navd/navincidentd.cpython-312-aarch64-linux-gnu.so": "5ybfD0zGZHLat8RUcQGgJmF2z4yPFEBUmMTp5+BdYP4IYa9m5rbUxAXLQSHUAFfM4X3pHSl4pVkBk8Uz30g3AA==", - "python/iqpilot_private/navd/navrenderd.cpython-312-aarch64-linux-gnu.so": "oabGQM48/eoQDLo1/csw/nzGCBAaoUmalk1I6lO0yPmh16wvweGrUZ81IB9oIjmeQYmUcdsKbSi1HMlvV6f2Cw==", - "python/iqpilot_private/navd/osm_cameras.cpython-312-aarch64-linux-gnu.so": "DwBKUAvtohLfZSwMDTYfEU8ghoG5LGKQYOB8j/8y77dCSng5kjPL9kvnOXwO4F/SfK/VqjOpMbQyK6V4EVvtCw==", - "python/iqpilot_private/navd/reroute.cpython-312-aarch64-linux-gnu.so": "CULSqD7g2vYgxtB+FLZMVGUY/tvtLXbd/jzmA7xt1W2Wla6cV4OS+xKXz6tpLA0o0MQFVUwFHEp4xMMbZ/kqAA==", - "python/iqpilot_private/navd/road_incidents.cpython-312-aarch64-linux-gnu.so": "X/FYviFDp4KlDv3qhVpvXVIDcC8dL0NcxyTKXN4DRy1/9PGIAvydNIl4ViLR6JcP7hFd+KeEXT4WjaXrxJUHCw==", - "python/iqpilot_private/navd/route_manager.cpython-312-aarch64-linux-gnu.so": "QWPpQTkK/589D84nSAqtauyiLwHZRlcRwFSmubLyRr8xv2UKh+vfHSHT+s3EUYcl73k3tJrgQlNZAqNU+RVEAA==", - "python/iqpilot_private/navd/runtime_common.cpython-312-aarch64-linux-gnu.so": "bE6I2JK6hfoWkhpJ6m8zgK6ZXDWignEQE+titwgQkYU+BXv1W7ZjxW5anHwTN5N/nXu3nDzRPxQmATRB4QYEDw==", - "python/iqpilot_private/navd/traffic.cpython-312-aarch64-linux-gnu.so": "F+oel9VPRD18u70lYd7h3Qw7Ispwm8ttsPORlUvDZUqcxPvtL5vYt8gZ7joTRA3hdyrDqLEXvLOEUyEljJwGAA==", - "python/iqpilot_private/navd/turn_desire.cpython-312-aarch64-linux-gnu.so": "ytdGaSS1/ME/0LZFzmQ+DMjWsToxQAWRqVMCv+gHYOYwM8ye3xjJwR2mi5rVfI2CvyWM4XT2AXRNfTotEDEVAA==", - "python/iqpilot_private/navd/web_server.cpython-312-aarch64-linux-gnu.so": "Wll0YO5ZFHblaCKwip3269WhENgac4VII8DUdA0G51V3Ru/SxxXetW6LSnJuCERYYnhwqEM5SxizXrVJSIYmCA==" + "python/iqpilot_private/navd/__init__.cpython-312-aarch64-linux-gnu.so": "oPML6Wc2Gz1fcV9/pvBqum19QGaDdU6dgx4XxyW+riBWG0SCYhQN49tYqIlCYG0sFhmNTxk+t7PooiDR6IFGDA==", + "python/iqpilot_private/navd/alpr_matcher.cpython-312-aarch64-linux-gnu.so": "ny5TsakqUUusA8CHaRAHr2u8I36TJMEUYUlIj6QVyLL2+lSdx4EILBp4CPxlCbKD0GXHmtz7e3cUXjQ8ZeEwAg==", + "python/iqpilot_private/navd/amap_client.cpython-312-aarch64-linux-gnu.so": "NphgdvZnPhZGkKWHxb9MGuZBfNMIiZPAFZ52c554F0894KWrInZNVgejQO7j+uz2QnS3ZQdR/PlFmI1kdDi+Aw==", + "python/iqpilot_private/navd/event_builder.cpython-312-aarch64-linux-gnu.so": "adGUan4KdFX6rUvqTlURR6ZRPiW5H3ssuIR66TRmNbTUetkU6dmNNl4YhEslaKSz1cYQHyEpHwdU2P+DjwKDCg==", + "python/iqpilot_private/navd/helpers.cpython-312-aarch64-linux-gnu.so": "NXYguIzqCJ0R2KFlweClB4KVYpLmWhVo125EtvYFqKYEQrPQFc9gPv5z9ximd4uJkeTDSJbDBEtgESscs2tyAA==", + "python/iqpilot_private/navd/incident_feed.cpython-312-aarch64-linux-gnu.so": "ng6Y0DD3j+U/Y6LLH5yK5Z5di/FtAnze+nU6EOlE/5v8a0ITw0TDKe90OeiUUWV+vH580sec0DwzNuFyxKodAA==", + "python/iqpilot_private/navd/iqmapd.cpython-312-aarch64-linux-gnu.so": "HihBFDE4shygX42LHhQK3+FBqEtwDhpdkgavEzIi4o9G1IoDHJw3x1waUYq4f53pVORmHdigRlhCHAzCu0QBCA==", + "python/iqpilot_private/navd/long_decel.cpython-312-aarch64-linux-gnu.so": "XTu5FBSn9wzzUbyFpYqtEI6T8xFyIaY4JheqW2laPKx+opAhY8OGyniRdc8WQm28/YkYh7alFHcbmkE0ZRMOAw==", + "python/iqpilot_private/navd/mapbox_client.cpython-312-aarch64-linux-gnu.so": "KOXmWcb8Pv+Y8h2hbAVu86CfKvwM2STRljCkiaLFayPa+s7ItISkwwLeaj94EQpg7QW0MGKGXBznZJRHBtM9DA==", + "python/iqpilot_private/navd/nav_cameras.cpython-312-aarch64-linux-gnu.so": "2XWDcUR+oJH0invIRcOfERVwwUhC8ATks+gYWHMw8FlTKjSqbLhzY3TyWB2c1MIpF311c968R0wc4ZmCmmdSDw==", + "python/iqpilot_private/navd/nav_turn_signals.cpython-312-aarch64-linux-gnu.so": "uR1wgK2h8ouVr+Po1brESGiDhbpxBEBqFGq9Ek+woiaceJO9njiTKSOBSldp3653sGIPuZ4++7yRMmzCd7GDCw==", + "python/iqpilot_private/navd/navd.cpython-312-aarch64-linux-gnu.so": "zMtyBW+eW3Phtljgoq8dEHThhyeenDq9IPkRCPiWzdZoSysKWmOz0cDnhuLihgaJJDcOz4msp5aggqzHsKlRCQ==", + "python/iqpilot_private/navd/navincidentd.cpython-312-aarch64-linux-gnu.so": "J3ZnYTzWLxwDUrmwh+d2rKmtMM/t6eGfSYQQoihLltX5LfXZHRUjnk/CMbr619iJWw2srmX/HT2LWyjh+h7vBw==", + "python/iqpilot_private/navd/navrenderd.cpython-312-aarch64-linux-gnu.so": "E340TW5CssaeUeA/xioykmrvsVJUZDVe5mUpUrdCUyG6qVta/V+6ZxvM+n9FEjUuqHO5w0U1Gog7SMU6QguEDA==", + "python/iqpilot_private/navd/osm_cameras.cpython-312-aarch64-linux-gnu.so": "l3iGDUFF+LGssB6mrgoOoB/P4M4FeoDQmmZgYzjoQArM0DjXN51yHC1oAuVUikMC1QqI8JEz+pT/t3K/lthDDA==", + "python/iqpilot_private/navd/reroute.cpython-312-aarch64-linux-gnu.so": "dR976C5dqTNV2z2I9RXcfxT/SSwg+IRaIcXrJM6/uL2rjyTW4E0avjJwgIH7GAYyOKfdRdWaBhoeIpfpDu18Aw==", + "python/iqpilot_private/navd/road_incidents.cpython-312-aarch64-linux-gnu.so": "n5h8gG43Se5sGYqwc+OAmu2JF2mWKZ6yHz+Zm62qwYuImz/3KMC8dP4tXWrCsZNeOYhIWhQ8/rtrUA2+99GDBg==", + "python/iqpilot_private/navd/route_manager.cpython-312-aarch64-linux-gnu.so": "pnE75vxRBGvNdNGna3EETR5/XIVm6++qFo+RiwII/Cfr/qLy2htTdGRv7sVz419HR65NbrK1XNOhnomgaeKvCg==", + "python/iqpilot_private/navd/runtime_common.cpython-312-aarch64-linux-gnu.so": "wbXLXRjX9hsV1fhYcHhqii6+ejytkL8cBWROmUjc4S0zJCMj5+h+egXl8Pdg7R2X0ylFM6Ifxa3xvyT6CwMuDA==", + "python/iqpilot_private/navd/traffic.cpython-312-aarch64-linux-gnu.so": "DztyZU20SV8yBmCxA8RVRQQgUyOsKYPpvFaXlOOI7qrlWEJUFedw1CPhh+OEfPtqETHtAD/RbYtNTJvJxdOEBA==", + "python/iqpilot_private/navd/turn_desire.cpython-312-aarch64-linux-gnu.so": "Zuif6kcPBjB3x+fN9BSrYv0RVMplHLGtV5grV0j8iNLOvO9vgKveAxSQYm+Mds1qjLOheL6ElhOPrlHH93ZlCg==", + "python/iqpilot_private/navd/web_server.cpython-312-aarch64-linux-gnu.so": "9uBrvcgtzx/3Y4GUaFW2M3DD6got1o+ZtQRjPUC3FZtWLRO4nl4U/6iBRuNXnSOw5Hv2M5P4OZawSo6stK/MAQ==" } } diff --git a/artifacts/iqpilot_navd_private/manifest.json.sig b/artifacts/iqpilot_navd_private/manifest.json.sig index 114214fef..5bd2549a3 100644 --- a/artifacts/iqpilot_navd_private/manifest.json.sig +++ b/artifacts/iqpilot_navd_private/manifest.json.sig @@ -1 +1 @@ -qir1it9kZm0rKWklTiwgR7rtCnv5YABlSkR4Mg+0lGlsBrw7mWRFJ81zSR+uOTgJllPAugTSzlAck8lHpXZCAQ== +WJGvk92Vi3ftfwgoTBFWBFrdBiRyw1rJNRMjFcAsqEAcbBlA3tZWbUNmqLqiJamhrGc7pWX/1j2jEB0dImvZBg== diff --git a/artifacts/iqpilot_navd_private/python/_iqclosure/iqpilot/selfdrive/car/cruise.py b/artifacts/iqpilot_navd_private/python/_iqclosure/iqpilot/selfdrive/car/cruise.py index f54864f75..79881caee 100644 --- a/artifacts/iqpilot_navd_private/python/_iqclosure/iqpilot/selfdrive/car/cruise.py +++ b/artifacts/iqpilot_navd_private/python/_iqclosure/iqpilot/selfdrive/car/cruise.py @@ -6,6 +6,7 @@ from iqpilot.common.constants import CV from iqpilot.cereal import car, custom from iqdbc.car import structs from iqpilot.common.params import Params +from iqpilot.common.realtime import DT_CTRL from iqpilot.selfdrive.car.long_increments import LongIncrementConfig, read_long_increment_config, resolve_button_step @@ -237,6 +238,35 @@ class VCruiseHelper(VCruiseHelperIQ): self.v_cruise_kph_last = 0 self.button_timers = {ButtonType.decelCruise: 0, ButtonType.accelCruise: 0} self.button_change_states = {btn: {"standstill": False, "enabled": False} for btn in self.button_timers} + self.slc_set_speed_request_id = 0 + self.slc_set_speed_gesture_id = 0 + self.slc_set_speed_request_kph = 0.0 + self._slc_accel_held = False + self._slc_accel_release_frames = 0 + self._slc_pcm_speed_last = None + + def _update_slc_accel_gesture(self, CS): + self._slc_accel_release_frames = max(0, self._slc_accel_release_frames - 1) + for button in CS.buttonEvents: + if button.type in (ButtonType.accelCruise, ButtonType.resumeCruise): + if button.pressed: + self.slc_set_speed_gesture_id = (self.slc_set_speed_gesture_id + 1) % (1 << 32) + self._slc_accel_release_frames = 0 + else: + self._slc_accel_release_frames = int(0.5 / DT_CTRL) + self._slc_accel_held = button.pressed + elif button.pressed: + self._slc_accel_held = False + self._slc_accel_release_frames = 0 + if not CS.cruiseState.available: + self._slc_accel_held = False + self._slc_accel_release_frames = 0 + + def _record_slc_set_speed_increase(self, previous_kph, enabled): + if enabled and (self._slc_accel_held or self._slc_accel_release_frames > 0) and \ + previous_kph is not None and 0 < previous_kph < self.v_cruise_kph <= V_CRUISE_MAX: + self.slc_set_speed_request_id = (self.slc_set_speed_request_id + 1) % (1 << 32) + self.slc_set_speed_request_kph = float(self.v_cruise_kph) @property def v_cruise_initialized(self): @@ -248,6 +278,7 @@ class VCruiseHelper(VCruiseHelperIQ): def update_v_cruise(self, CS, enabled, is_metric): self.v_cruise_kph_last = self.v_cruise_kph + self._update_slc_accel_gesture(CS) self.get_minimum_set_speed(is_metric) @@ -256,12 +287,15 @@ class VCruiseHelper(VCruiseHelperIQ): if not self.CP.pcmCruise or (not self.CP_IQ.pcmCruiseSpeed and _enabled): # if stock cruise is completely disabled, then we can use our own set speed logic self._update_v_cruise_non_pcm(CS, _enabled, is_metric, self.volkswagen_standby_set_speed) + self._record_slc_set_speed_increase(self.v_cruise_kph_last, _enabled) self.update_speed_limit_assist_v_cruise_non_pcm() self.v_cruise_cluster_kph = self.v_cruise_kph self.update_button_timers(CS, enabled) else: self.v_cruise_kph = CS.cruiseState.speed * CV.MS_TO_KPH self.v_cruise_cluster_kph = CS.cruiseState.speedCluster * CV.MS_TO_KPH + self._record_slc_set_speed_increase(self._slc_pcm_speed_last, _enabled) + self._slc_pcm_speed_last = self.v_cruise_kph if CS.cruiseState.speed == 0: self.v_cruise_kph = V_CRUISE_UNSET self.v_cruise_cluster_kph = V_CRUISE_UNSET @@ -273,6 +307,7 @@ class VCruiseHelper(VCruiseHelperIQ): else: self.v_cruise_kph = V_CRUISE_UNSET self.v_cruise_cluster_kph = V_CRUISE_UNSET + self._slc_pcm_speed_last = None def _update_v_cruise_non_pcm(self, CS, enabled, is_metric, allow_standby_adjustment=False): # handle button presses. TODO: this should be in state_control, but a decelCruise press diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/__init__.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/__init__.cpython-312-aarch64-linux-gnu.so index ea6bc22dc..cc946280f 100755 Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/__init__.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/__init__.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/alpr_matcher.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/alpr_matcher.cpython-312-aarch64-linux-gnu.so new file mode 100755 index 000000000..0a7201557 Binary files /dev/null and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/alpr_matcher.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/amap_client.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/amap_client.cpython-312-aarch64-linux-gnu.so index 29ab7e266..edfae3d3b 100755 Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/amap_client.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/amap_client.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/event_builder.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/event_builder.cpython-312-aarch64-linux-gnu.so index 0647cccec..05993b6a0 100755 Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/event_builder.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/event_builder.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/helpers.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/helpers.cpython-312-aarch64-linux-gnu.so index 7f1bd54fa..e34dcda74 100755 Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/helpers.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/helpers.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/incident_feed.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/incident_feed.cpython-312-aarch64-linux-gnu.so index a75bc5b32..b12a0a329 100755 Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/incident_feed.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/incident_feed.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/iqmapd.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/iqmapd.cpython-312-aarch64-linux-gnu.so index 5cf37e9a9..3df09f3fa 100755 Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/iqmapd.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/iqmapd.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/long_decel.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/long_decel.cpython-312-aarch64-linux-gnu.so index 88d697ed8..2b5d03c5a 100755 Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/long_decel.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/long_decel.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/mapbox_client.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/mapbox_client.cpython-312-aarch64-linux-gnu.so index 4366c3693..2411f44dc 100755 Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/mapbox_client.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/mapbox_client.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/nav_cameras.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/nav_cameras.cpython-312-aarch64-linux-gnu.so index a1440344b..6050ea526 100755 Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/nav_cameras.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/nav_cameras.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/nav_turn_signals.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/nav_turn_signals.cpython-312-aarch64-linux-gnu.so index d79b0a7cc..0c742324d 100755 Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/nav_turn_signals.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/nav_turn_signals.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/navd.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/navd.cpython-312-aarch64-linux-gnu.so index 269f3d7d6..25dba131e 100755 Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/navd.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/navd.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/navincidentd.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/navincidentd.cpython-312-aarch64-linux-gnu.so index efe09b6cb..f2a97f3ff 100755 Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/navincidentd.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/navincidentd.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/navrenderd.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/navrenderd.cpython-312-aarch64-linux-gnu.so index 7eabfab1c..bd1dbd070 100755 Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/navrenderd.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/navrenderd.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/osm_cameras.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/osm_cameras.cpython-312-aarch64-linux-gnu.so index 90b102d01..4ab1d77fe 100755 Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/osm_cameras.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/osm_cameras.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/reroute.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/reroute.cpython-312-aarch64-linux-gnu.so index b77cbdfa6..2eec042c8 100755 Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/reroute.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/reroute.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/road_incidents.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/road_incidents.cpython-312-aarch64-linux-gnu.so index d1b636ff6..ff3e3234d 100755 Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/road_incidents.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/road_incidents.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/route_manager.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/route_manager.cpython-312-aarch64-linux-gnu.so index a9b4505b8..9cddcff33 100755 Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/route_manager.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/route_manager.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/runtime_common.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/runtime_common.cpython-312-aarch64-linux-gnu.so index 8c8cf5edb..fadc745d6 100755 Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/runtime_common.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/runtime_common.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/traffic.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/traffic.cpython-312-aarch64-linux-gnu.so index 362de49be..523ad089c 100755 Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/traffic.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/traffic.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/turn_desire.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/turn_desire.cpython-312-aarch64-linux-gnu.so index 6c5640a05..29b8610db 100755 Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/turn_desire.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/turn_desire.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/web_server.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/web_server.cpython-312-aarch64-linux-gnu.so index 3b682ed4c..81d64dff5 100755 Binary files a/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/web_server.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_navd_private/python/iqpilot_private/navd/web_server.cpython-312-aarch64-linux-gnu.so differ diff --git a/iqpilot/common/params_keys.h b/iqpilot/common/params_keys.h index 47cfece36..0cb80d796 100644 --- a/iqpilot/common/params_keys.h +++ b/iqpilot/common/params_keys.h @@ -474,6 +474,7 @@ inline static std::unordered_map keys = { {"SpeedCameraSlowdown", {PERSISTENT, BOOL, "0"}}, {"SpeedCameraSafetyFactor", {PERSISTENT, FLOAT, "1.0"}}, {"NavCamerasData", {PERSISTENT, JSON}}, + {"NavCameraRoadGraphData", {PERSISTENT, JSON}}, {"WazePoliceApiKey", {PERSISTENT | DONT_LOG, STRING}}, {"WazePoliceAlertMode", {PERSISTENT, INT, "0"}}, {"WazePoliceShadow", {PERSISTENT, BOOL, "0"}}, diff --git a/iqpilot/selfdrive/iqmodeld/egpu_helpers.py b/iqpilot/selfdrive/iqmodeld/egpu_helpers.py index 9e953fb9a..1317d78be 100644 --- a/iqpilot/selfdrive/iqmodeld/egpu_helpers.py +++ b/iqpilot/selfdrive/iqmodeld/egpu_helpers.py @@ -6,12 +6,16 @@ from __future__ import annotations import hashlib import json import os + +from iqpilot.common.swaglog import cloudlog import urllib.request from pathlib import Path from iqpilot.system.hardware.usb import egpu_dock_ready USB_SYSFS_ROOT = "/sys/bus/usb/devices" +FIRMWARE_MIRROR = os.getenv("IQ_EGPU_FIRMWARE_MIRROR", "/data/firmware/tinygrad") +TINYGRAD_CACHE = "/data/.cache" COMMA_LFS_BATCH_URL = "https://gitlab.com/commaai/openpilot-lfs.git/info/lfs/objects/batch" @@ -62,6 +66,11 @@ def egpu_policy_pkl_path(meta: dict) -> str: return os.path.join(Paths.model_root(), f"egpu_{meta['key']}_{meta['sha256'][:8]}_amd_policy.pkl") +def egpu_oob_pkl_path(meta: dict) -> str: + from iqpilot.system.hardware.hw import Paths + return os.path.join(Paths.model_root(), f"egpu_{meta['key']}_{meta['sha256'][:8]}_amd_policy_oob.pkl") + + def onnx_cache_path(meta: dict) -> str: from iqpilot.system.hardware.hw import Paths return os.path.join(Paths.model_root(), f"{meta['model_name']}_{meta['sha256'][:8]}.onnx") @@ -119,9 +128,14 @@ def download_onnx(meta: dict, progress_cb=None) -> str: if not download_url: raise RuntimeError(f"model {meta['key']} has no download source; stage the onnx at {onnx_cache_path(meta)}") - url = resolve_download_url(download_url, meta["sha256"], size) path = onnx_cache_path(meta) os.makedirs(os.path.dirname(path), exist_ok=True) + try: + from iqpilot.selfdrive.iqmodeld.model_bundle_downloader import download_hf_file + return download_hf_file(f"onnx/{meta['sha256']}.onnx", path, meta["sha256"], int(size or 0), progress_cb=progress_cb) + except Exception as e: + cloudlog.warning(f"onnx {meta['key']} unavailable from HF ({e}); falling back to {download_url.split(':', 1)[0]}") + url = resolve_download_url(download_url, meta["sha256"], size) tmp = path + ".part" digest = hashlib.sha256() got = 0 @@ -142,12 +156,20 @@ def download_onnx(meta: dict, progress_cb=None) -> str: return path -def download_precompiled(meta: dict, progress_cb=None, policy: bool = False) -> str | None: - art = meta.get("egpu_policy_artifact" if policy else "egpu_artifact") - if not art or not art.get("objects"): +def download_precompiled(meta: dict, progress_cb=None, policy: bool = False, oob: bool = False) -> str | None: + field = "egpu_oob_artifact" if oob else "egpu_policy_artifact" if policy else "egpu_artifact" + art = meta.get(field) + if not art or not (art.get("objects") or art.get("hf_path")): return None - from iqpilot.selfdrive.iqmodeld.model_bundle_downloader import download_lfs_bundle - dest = egpu_policy_pkl_path(meta) if policy else egpu_pkl_path(meta) + from iqpilot.selfdrive.iqmodeld.model_bundle_downloader import download_hf_file, download_lfs_bundle + dest = egpu_oob_pkl_path(meta) if oob else egpu_policy_pkl_path(meta) if policy else egpu_pkl_path(meta) + if art.get("hf_path"): + try: + return download_hf_file(art["hf_path"], dest, art["sha256"], int(art.get("size", 0)), progress_cb=progress_cb) + except Exception as e: + cloudlog.warning(f"precompiled {meta['key']} unavailable from HF ({e}); trying LFS") + if not art.get("objects"): + raise return download_lfs_bundle(art["objects"], dest, art["sha256"], int(art.get("size", 0)), progress_cb=progress_cb) @@ -161,12 +183,27 @@ def patch_tinygrad_fetch_fw() -> None: _orig = helpers.fetch_fw def fetch_fw(path, name, sha256): + mirror = pathlib.Path(FIRMWARE_MIRROR) / path / name + if mirror.is_file(): + blob = mirror.read_bytes() + if hashlib.sha256(blob).hexdigest() == sha256: + return blob p = pathlib.Path(f"/lib/firmware/{path}/{name}.zst") if p.is_file(): blob = zstandard.ZstdDecompressor().stream_reader(p.read_bytes()).read() if hashlib.sha256(blob).hexdigest() == sha256: return blob - return _orig(path, name, sha256) + blob = _orig(path, name, sha256) + # The dock's GPU firmware otherwise lives only in tinygrad's per-user download cache, which is + # a network fetch the first time a new HOME sees it; onroad the car is usually offline. + try: + mirror.parent.mkdir(parents=True, exist_ok=True) + tmp = mirror.with_suffix(mirror.suffix + ".part") + tmp.write_bytes(blob) + os.replace(tmp, mirror) + except OSError: + pass + return blob fetch_fw._iq_patched = True helpers.fetch_fw = fetch_fw diff --git a/iqpilot/selfdrive/iqmodeld/egpu_policy.py b/iqpilot/selfdrive/iqmodeld/egpu_policy.py index 726df1e66..2d0b12d7a 100644 --- a/iqpilot/selfdrive/iqmodeld/egpu_policy.py +++ b/iqpilot/selfdrive/iqmodeld/egpu_policy.py @@ -3,11 +3,18 @@ Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed """ from __future__ import annotations +import io import math +import os +import pickle +import shutil +import struct +import tempfile import numpy as np POLICY_FORMAT = 2 +OOB_MAGIC = b"IQEGPUOOB1" QUEUE_NAMES = ("img_q", "big_img_q", "feat_q", "desire_q") PACKED_ORDER = ("desire", "traffic_convention", "action_t", "prev_feat") @@ -115,3 +122,50 @@ class PolicyRunner: flat = out.numpy().reshape(-1) v["prev_feat"][:] = flat[self._hidden].reshape(v["prev_feat"].shape) return flat + + +def dump_oob(obj, f) -> None: + # Out-of-band pickle buffers keep the host peak at one tensor while the weights stream to the + # dock; a plain pickle keeps every weight referenced in the memo until load() returns (~1.7GB). + f.write(OOB_MAGIC) + with tempfile.TemporaryFile(dir=os.path.dirname(os.path.abspath(f.name)) or ".") as tmp: + def buffer_callback(pb: pickle.PickleBuffer): + m = pb.raw() + tmp.write(struct.pack(" bool: + with open(path, "rb") as f: + return f.read(len(OOB_MAGIC)) == OOB_MAGIC + + +def load_oob(f): + if f.read(len(OOB_MAGIC)) != OOB_MAGIC: + raise ValueError("not an out-of-band bundle") + opcodes = f.read(struct.unpack(" dict | None: + key = params.get("IQEmacModel", encoding="utf8") + try: + return resolve_egpu_model(params, key) + except Exception as e: + cloudlog.warning(f"egpu_prefetch cannot resolve {key!r}: {e}") + return None + + +def _drop_stale_partials(keep: str) -> None: + root = os.path.dirname(keep) + for path in glob.glob(os.path.join(root, "egpu_*_amd_policy.pkl.part")) + glob.glob(os.path.join(root, "big_driving_supercombo_*.onnx.part")): + if not path.startswith(keep): + try: + os.remove(path) + except OSError: + pass + + +def prefetch_once(params: Params) -> bool: + if not usbgpu_present() or not egpu_selected(params): + return False + meta = _selected_meta(params) + if meta is None: + return False + dst = egpu_policy_pkl_path(meta) + if os.path.isfile(dst): + return True + if not meta.get("egpu_policy_artifact"): + return False + _drop_stale_partials(dst) + params.put("UsbGpuSetupProgress", "0.0") + last = [-1.0] + + def _prog(p: float) -> None: + if p - last[0] >= 0.02 or p >= 1.0: + last[0] = p + params.put("UsbGpuSetupProgress", f"{p:.3f}") + + cloudlog.warning(f"egpu_prefetch downloading {meta['key']} policy artifact offroad") + out = download_precompiled(meta, progress_cb=_prog, policy=True) + cloudlog.warning(f"egpu_prefetch ready -> {out}") + return out is not None + + +_firmware_warm = False + + +def warm_firmware() -> None: + global _firmware_warm + if _firmware_warm or not usbgpu_present(): + return + os.environ.setdefault("DEV", "USB+AMD:LLVM") + os.environ.setdefault("GMMU", "0") + patch_tinygrad_fetch_fw() + from tinygrad.device import Device + Device["AMD"] + _firmware_warm = True + cloudlog.warning("egpu_prefetch: dock opened offroad; firmware cached and mirrored") + + +def main() -> None: + params = Params() + while True: + try: + warm_firmware() + except Exception as e: + cloudlog.warning(f"egpu_prefetch firmware warm failed: {e}") + try: + prefetch_once(params) + except Exception as e: + cloudlog.warning(f"egpu_prefetch failed: {e}") + params.put("UsbGpuLastError", str(e)[:512]) + time.sleep(RETRY_S) + continue + time.sleep(POLL_S) + + +if __name__ == "__main__": + main() diff --git a/iqpilot/selfdrive/iqmodeld/iqegpumodeld.py b/iqpilot/selfdrive/iqmodeld/iqegpumodeld.py index c8fadbe4e..69cc190aa 100644 --- a/iqpilot/selfdrive/iqmodeld/iqegpumodeld.py +++ b/iqpilot/selfdrive/iqmodeld/iqegpumodeld.py @@ -4,6 +4,7 @@ Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed from __future__ import annotations import os +os.environ.setdefault("XDG_CACHE_HOME", "/data/.cache") import pickle import subprocess import sys @@ -38,7 +39,7 @@ from iqpilot.selfdrive.iqmodeld.driving_action import ( DESIRE_LEN, LAT_SMOOTH_SECONDS, LONG_SMOOTH_SECONDS, get_action_from_model, ) from iqpilot.selfdrive.iqmodeld.egpu_helpers import ( - download_onnx, download_precompiled, egpu_pkl_path, egpu_policy_pkl_path, egpu_present_consented, egpu_selected, local_onnx, + download_onnx, download_precompiled, egpu_oob_pkl_path, egpu_pkl_path, egpu_policy_pkl_path, egpu_present_consented, egpu_selected, local_onnx, patch_tinygrad_fetch_fw, quarantine_artifact, resolve_backend, usbgpu_present, ) from iqpilot.selfdrive.iqmodeld.egpu_model import resolve_egpu_model @@ -47,7 +48,7 @@ from iqpilot.selfdrive.iqmodeld.egpu_telemetry import EgpuDockTelemetry from iqpilot.selfdrive.iqmodeld.messaging import DrivePacketMemory, populate_drive_messages, populate_odometry_message from iqpilot.selfdrive.iqmodeld.metadata import Meta20hz from iqpilot.selfdrive.iqmodeld.model_channel import BIG_CHANNEL, ModelChannel -from iqpilot.selfdrive.iqmodeld.egpu_policy import POLICY_FORMAT, PolicyRunner +from iqpilot.selfdrive.iqmodeld.egpu_policy import POLICY_FORMAT, PolicyRunner, load_bundle from iqpilot.selfdrive.iqmodeld.model_warp import FrameWarp from iqpilot.selfdrive.iqmodeld.parser import PhaseParser @@ -56,6 +57,9 @@ PROCESS_NAME = "iqpilot.selfdrive.iqmodeld.iqegpumodeld" PRESENCE_POLL_S = 5.0 COMPILE_TIMEOUT_S = 3600 LINK_UP_TIMEOUT_S = 10.0 +SETUP_EXIT_AFTER = 3 +MIN_LOAD_AVAIL_MB = 350 +MEMORY_WAIT_S = 90.0 SETUP_RETRY_BASE_S = 3.0 SETUP_RETRY_MAX_S = 30.0 @@ -106,14 +110,38 @@ _precompiled_tried = False def _ensure_artifact(params: Params, meta: dict) -> str: global _precompiled_tried + oob_path = egpu_oob_pkl_path(meta) + if os.path.isfile(oob_path): + return oob_path policy_path = egpu_policy_pkl_path(meta) - if os.path.isfile(policy_path): - return policy_path legacy_path = egpu_pkl_path(meta) params.put_bool("UsbGpuCompiled", False) params.put_bool("UsbGpuReady", False) + if meta.get("egpu_oob_artifact") and not _precompiled_tried: + _precompiled_tried = True + params.put("UsbGpuSetupProgress", "0.0") + oob_last = [-1.0] + + def _oob_prog(p: float) -> None: + if p - oob_last[0] >= 0.02 or p >= 1.0: + oob_last[0] = p + params.put("UsbGpuSetupProgress", f"{p:.3f}") + + try: + cloudlog.warning(f"iqegpumodeld downloading precompiled {meta['key']} (streamable) " + f"({int(meta['egpu_oob_artifact'].get('size', 0)) / 1e6:.0f}MB)") + precompiled = download_precompiled(meta, progress_cb=_oob_prog, oob=True) + if precompiled is not None: + cloudlog.warning(f"iqegpumodeld precompiled ready -> {precompiled}") + return precompiled + except Exception as e: + cloudlog.warning(f"iqegpumodeld streamable artifact unavailable ({e}); falling back") + + if os.path.isfile(policy_path): + return policy_path + if meta.get("egpu_policy_artifact") and not _precompiled_tried: _precompiled_tried = True params.put("UsbGpuSetupProgress", "0.0") @@ -157,12 +185,34 @@ def _ensure_artifact(params: Params, meta: dict) -> str: return policy_path +def _mem_available_mb() -> int: + try: + with open("/proc/meminfo") as f: + for line in f: + if line.startswith("MemAvailable:"): + return int(line.split()[1]) // 1024 + except OSError: + pass + return 1 << 20 + + +def _wait_for_memory(need_mb: int) -> None: + deadline = time.monotonic() + MEMORY_WAIT_S + avail = _mem_available_mb() + while avail < need_mb and time.monotonic() < deadline: + cloudlog.warning(f"iqegpumodeld waiting for memory: {avail}MB available, need {need_mb}MB") + time.sleep(5.0) + avail = _mem_available_mb() + if avail < need_mb: + raise RuntimeError(f"insufficient memory to load the dock model: {avail}MB available, need {need_mb}MB") + + def _load_infer_fn(pkl_path: str, meta: dict): patch_tinygrad_fetch_fw() from tinygrad.tensor import Tensor - with open(pkl_path, "rb") as f: - bundle = pickle.load(f) + _wait_for_memory(MIN_LOAD_AVAIL_MB) + bundle = load_bundle(pkl_path) if bundle.get("model_sha256") != meta["sha256"]: quarantine_artifact(pkl_path, "pkl model sha mismatch") raise RuntimeError(f"artifact model sha {bundle.get('model_sha256')} != {meta['sha256']}") @@ -241,8 +291,14 @@ def main(demo: bool = False) -> None: break except Exception as e: attempt += 1 - params.put("UsbGpuLastError", str(e)[:512]) - cloudlog.warning(f"iqegpumodeld setup attempt {attempt} failed: {e}; retrying") + subs = "; ".join(f"{type(x).__name__}: {x}" for x in (getattr(e, "exceptions", None) or [])) + params.put("UsbGpuLastError", (f"{e} [{subs}]" if subs else str(e))[:512]) + cloudlog.warning(f"iqegpumodeld setup attempt {attempt} failed: {e}; {subs}; retrying") + if attempt >= SETUP_EXIT_AFTER: + # tinygrad keeps the dock's flock in a failed device init, so a stale process can never + # reopen it; exit and let the manager respawn a clean one. + cloudlog.error(f"iqegpumodeld giving up after {attempt} setup failures; exiting for a clean restart") + sys.exit(1) if not usbgpu_present(): _wait_for_egpu(params) time.sleep(min(SETUP_RETRY_MAX_S, SETUP_RETRY_BASE_S * attempt)) diff --git a/iqpilot/selfdrive/iqmodeld/model_bundle_downloader.py b/iqpilot/selfdrive/iqmodeld/model_bundle_downloader.py index 35ac6acaf..f71e29594 100644 --- a/iqpilot/selfdrive/iqmodeld/model_bundle_downloader.py +++ b/iqpilot/selfdrive/iqmodeld/model_bundle_downloader.py @@ -27,6 +27,63 @@ def _requests_auth(): return None +def _hf(): + import importlib + for mod in ("iqpilot_private.models.git_auth", "iqpilot.selfdrive.iqmodeld.models.git_auth"): + try: + m = importlib.import_module(mod) + return m.get_hf_headers(), m.hf_resolve_url + except Exception: + continue + return None, None + + +def download_hf_file(hf_path: str, dst: str, sha256: str, size: int, progress_cb=None) -> str: + import requests + headers, resolve = _hf() + if resolve is None: + raise RuntimeError("no HF credentials available") + url = resolve(hf_path) + os.makedirs(os.path.dirname(dst), exist_ok=True) + tmp = dst + ".hfpart" + last_error: Exception | None = None + for _attempt in range(STREAM_RETRIES): + try: + have = os.path.getsize(tmp) if os.path.isfile(tmp) else 0 + if size and have > size: + os.remove(tmp) + have = 0 + if not size or have < size: + req_headers = dict(headers) + if have: + req_headers["Range"] = f"bytes={have}-" + with requests.get(url, headers=req_headers, stream=True, timeout=HTTP_TIMEOUT_S, allow_redirects=True) as r: + r.raise_for_status() + if have and r.status_code != 206: + have = 0 + with open(tmp, "ab" if have else "wb") as f: + got = have + for chunk in r.iter_content(CHUNK): + f.write(chunk) + got += len(chunk) + if progress_cb is not None and size: + progress_cb(min(1.0, got / size)) + digest = hashlib.sha256() + with open(tmp, "rb") as f: + for chunk in iter(lambda: f.read(CHUNK), b""): + digest.update(chunk) + if size and os.path.getsize(tmp) != size: + raise RuntimeError(f"size mismatch: {os.path.getsize(tmp)}/{size} bytes") + if sha256 and digest.hexdigest() != sha256: + os.remove(tmp) + raise RuntimeError("sha256 mismatch") + os.replace(tmp, dst) + return dst + except Exception as e: + last_error = e + raise RuntimeError(f"HF download failed: {last_error}") + + def _lfs_endpoint(base_url: str) -> str: return base_url.split("/raw/", 1)[0] + ".git/info/lfs" @@ -47,41 +104,108 @@ def _resolve_oid(session, base_url: str, oid: str, size: int, auth): return action["href"], action.get("header", {}) +def _part_path(dst: str, oid: str) -> str: + return os.path.join(dst + ".parts", oid) + + +def _part_complete(path: str, oid: str, size: int) -> bool: + if not os.path.isfile(path) or os.path.getsize(path) != size: + return False + digest = hashlib.sha256() + with open(path, "rb") as f: + for chunk in iter(lambda: f.read(CHUNK), b""): + digest.update(chunk) + return digest.hexdigest() == oid + + +def _fetch_part(session, base_url: str, obj: dict, path: str, auth, progress) -> None: + size = int(obj["size"]) + have = os.path.getsize(path) if os.path.isfile(path) else 0 + if have > size: + os.remove(path) + have = 0 + href, headers = _resolve_oid(session, base_url, obj["oid"], size, auth) + obj_auth = None if headers.get("Authorization") else auth + # LFS parts are content-addressed (oid == sha256), so a half-written part can be resumed with a + # Range request and verified afterwards instead of being thrown away on every restart. + if have: + headers = {**headers, "Range": f"bytes={have}-"} + with session.get(href, headers=headers, stream=True, timeout=HTTP_TIMEOUT_S, auth=obj_auth) as r: + r.raise_for_status() + if have and r.status_code != 206: + have = 0 + with open(path, "ab" if have else "wb") as f: + for chunk in r.iter_content(CHUNK): + f.write(chunk) + progress(len(chunk)) + + def download_lfs_bundle(objects: list, dst: str, sha256: str, size: int, progress_cb=None) -> str: import requests auth = _requests_auth() session = requests.Session() - os.makedirs(os.path.dirname(dst), exist_ok=True) - tmp = dst + ".part" + os.makedirs(dst + ".parts", exist_ok=True) total = int(size) or sum(int(o["size"]) for o in objects) + done_bytes = sum(int(o["size"]) for o in objects if _part_complete(_part_path(dst, o["oid"]), o["oid"], int(o["size"]))) + got = [done_bytes] + + def progress(n: int) -> None: + got[0] += n + if progress_cb is not None and total: + progress_cb(min(1.0, got[0] / total)) + last_error: Exception | None = None for base_url in MODELS_BASE_URLS: - for attempt in range(STREAM_RETRIES): + for _attempt in range(STREAM_RETRIES): try: - digest = hashlib.sha256() - got = 0 - with open(tmp, "wb") as f: - for obj in objects: - href, headers = _resolve_oid(session, base_url, obj["oid"], int(obj["size"]), auth) - obj_auth = None if headers.get("Authorization") else auth - with session.get(href, headers=headers, stream=True, timeout=120, auth=obj_auth) as r: - r.raise_for_status() - for chunk in r.iter_content(CHUNK): - f.write(chunk) - digest.update(chunk) - got += len(chunk) - if progress_cb is not None and total: - progress_cb(min(1.0, got / total)) - if total and got != total: - raise RuntimeError(f"size mismatch: {got}/{total} bytes") - if sha256 and digest.hexdigest() != sha256: - raise RuntimeError("sha256 mismatch") - os.replace(tmp, dst) - return dst + for obj in objects: + path = _part_path(dst, obj["oid"]) + if _part_complete(path, obj["oid"], int(obj["size"])): + continue + got[0] = done_bytes + _fetch_part(session, base_url, obj, path, auth, progress) + if not _part_complete(path, obj["oid"], int(obj["size"])): + if os.path.getsize(path) >= int(obj["size"]): + os.remove(path) + raise RuntimeError(f"part {obj['oid'][:12]} incomplete or failed verification") + done_bytes += int(obj["size"]) + got[0] = done_bytes + break except Exception as e: last_error = e - try: - os.remove(tmp) - except OSError: - pass - raise RuntimeError(f"model bundle download failed: {last_error}") + else: + continue + break + else: + raise RuntimeError(f"model bundle download failed: {last_error}") + + tmp = dst + ".part" + digest = hashlib.sha256() + with open(tmp, "wb") as out: + for obj in objects: + with open(_part_path(dst, obj["oid"]), "rb") as f: + for chunk in iter(lambda: f.read(CHUNK), b""): + out.write(chunk) + digest.update(chunk) + if total and os.path.getsize(tmp) != total: + os.remove(tmp) + raise RuntimeError(f"size mismatch: {os.path.getsize(tmp) if os.path.exists(tmp) else 0}/{total} bytes") + if sha256 and digest.hexdigest() != sha256: + os.remove(tmp) + for obj in objects: + try: + os.remove(_part_path(dst, obj["oid"])) + except OSError: + pass + raise RuntimeError("sha256 mismatch") + os.replace(tmp, dst) + for obj in objects: + try: + os.remove(_part_path(dst, obj["oid"])) + except OSError: + pass + try: + os.rmdir(dst + ".parts") + except OSError: + pass + return dst diff --git a/iqpilot/selfdrive/iqmodeld/tests/test_egpu_firmware_mirror.py b/iqpilot/selfdrive/iqmodeld/tests/test_egpu_firmware_mirror.py new file mode 100644 index 000000000..5d51cdfe9 --- /dev/null +++ b/iqpilot/selfdrive/iqmodeld/tests/test_egpu_firmware_mirror.py @@ -0,0 +1,30 @@ +""" +Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/ +""" +import hashlib +import os + +from iqpilot.selfdrive.iqmodeld import egpu_helpers as eh + + +def test_fetch_fw_mirrors_and_serves_offline(tmp_path, monkeypatch): + from tinygrad import helpers + blob = os.urandom(4096) + sha = hashlib.sha256(blob).hexdigest() + calls = [] + + def orig(path, name, sha256): + calls.append((path, name)) + return blob + + monkeypatch.setattr(helpers, "fetch_fw", orig, raising=False) + helpers.fetch_fw._iq_patched = False + monkeypatch.setattr(eh, "FIRMWARE_MIRROR", str(tmp_path / "mirror")) + eh.patch_tinygrad_fetch_fw() + assert helpers.fetch_fw("amdgpu", "gc.bin", sha) == blob and calls == [("amdgpu", "gc.bin")] + mirrored = tmp_path / "mirror" / "amdgpu" / "gc.bin" + assert mirrored.read_bytes() == blob + assert helpers.fetch_fw("amdgpu", "gc.bin", sha) == blob and len(calls) == 1 + mirrored.write_bytes(b"corrupt") + assert helpers.fetch_fw("amdgpu", "gc.bin", sha) == blob and len(calls) == 2 + assert mirrored.read_bytes() == blob diff --git a/iqpilot/selfdrive/iqmodeld/tests/test_egpu_host_mock.py b/iqpilot/selfdrive/iqmodeld/tests/test_egpu_host_mock.py new file mode 100644 index 000000000..defe68d6c --- /dev/null +++ b/iqpilot/selfdrive/iqmodeld/tests/test_egpu_host_mock.py @@ -0,0 +1,34 @@ +""" +Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/ +""" +import os +import subprocess +import sys + +import pytest + +from iqpilot.selfdrive.iqmodeld.tools.egpu_host_mock import tinygrad_tree + +PROBE = """ +import os +os.environ["JIT_BATCH_SIZE"] = "0" +from iqpilot.selfdrive.iqmodeld.tools.egpu_host_mock import activate +activate("gfx1200") +from tinygrad import Tensor +from tinygrad.device import Device +from tinygrad.engine.jit import TinyJit +dev = Device["AMD"] +assert dev.arch == "gfx1200", dev.arch +assert type(dev.iface).__name__ == "MOCKUSBIface", type(dev.iface).__name__ +run = TinyJit(lambda x: (x * 2 + 1).sum(axis=1).realize()) +for i in range(3): + run(Tensor.ones(64, 64, device="AMD") * i) +print("MOCK_OK") +""" + + +@pytest.mark.skipif(not os.path.isdir(os.path.join(tinygrad_tree(), "test", "mockgpu")), reason="tinygrad mockgpu tree not checked out") +def test_mock_dock_captures_a_jit_without_hardware(): + out = subprocess.run([sys.executable, "-c", PROBE], capture_output=True, text=True, timeout=600) + assert out.returncode == 0, out.stderr[-2000:] + assert "MOCK_OK" in out.stdout diff --git a/iqpilot/selfdrive/iqmodeld/tests/test_egpu_oob.py b/iqpilot/selfdrive/iqmodeld/tests/test_egpu_oob.py new file mode 100644 index 000000000..008613e57 --- /dev/null +++ b/iqpilot/selfdrive/iqmodeld/tests/test_egpu_oob.py @@ -0,0 +1,62 @@ +""" +Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/ +""" +import os +import pickle + +import numpy as np +import pytest + +os.environ["DEV"] = "CPU" + +from iqpilot.selfdrive.iqmodeld.egpu_policy import dump_oob, is_oob, load_bundle + + +def _bundle(): + from tinygrad import Tensor + w = Tensor(np.arange(4096, dtype=np.float32).reshape(64, 64), device="CPU").realize() + return {"format": 2, "weights": w, "spec": {"a": ((1, 2), "float32")}, "blob": os.urandom(100_000)} + + +def test_oob_round_trip_matches_plain_pickle(tmp_path): + b = _bundle() + oob = tmp_path / "b.oob" + with open(oob, "wb") as f: + dump_oob(b, f) + assert is_oob(str(oob)) + got = load_bundle(str(oob)) + np.testing.assert_array_equal(got["weights"].numpy(), b["weights"].numpy()) + assert got["blob"] == b["blob"] and got["spec"] == b["spec"] and got["format"] == 2 + plain = tmp_path / "b.pkl" + with open(plain, "wb") as f: + pickle.dump({"x": 1, "blob": b["blob"]}, f, protocol=pickle.HIGHEST_PROTOCOL) + assert not is_oob(str(plain)) + assert load_bundle(str(plain))["blob"] == b["blob"] + + +def test_memory_guard_raises_when_starved(monkeypatch): + from iqpilot.selfdrive.iqmodeld import iqegpumodeld as d + monkeypatch.setattr(d, "_mem_available_mb", lambda: 90) + monkeypatch.setattr(d, "MEMORY_WAIT_S", 0.0) + with pytest.raises(RuntimeError, match="insufficient memory"): + d._wait_for_memory(350) + monkeypatch.setattr(d, "_mem_available_mb", lambda: 900) + d._wait_for_memory(350) + + +def test_opcode_rewrite_equals_oob_load(tmp_path): + from tinygrad import Tensor + from iqpilot.selfdrive.iqmodeld.tools.oob_rewrite import rewrite_oob + big = Tensor(np.random.default_rng(0).standard_normal((512, 512)).astype(np.float32), device="CPU").realize() + small = Tensor(np.arange(16, dtype=np.float32), device="CPU").realize() + b = {"format": 2, "w": big, "s": small, "meta": {"k": "v"}, "raw": os.urandom(200_000)} + plain = tmp_path / "plain.pkl" + with open(plain, "wb") as f: + pickle.dump(b, f, protocol=5) + oob = tmp_path / "oob.pkl" + moved, _ = rewrite_oob(str(plain), str(oob)) + assert moved >= 2 and is_oob(str(oob)) + got = load_bundle(str(oob)) + np.testing.assert_array_equal(got["w"].numpy(), b["w"].numpy()) + np.testing.assert_array_equal(got["s"].numpy(), b["s"].numpy()) + assert got["raw"] == b["raw"] and got["meta"] == {"k": "v"} diff --git a/iqpilot/selfdrive/iqmodeld/tests/test_model_bundle_downloader.py b/iqpilot/selfdrive/iqmodeld/tests/test_model_bundle_downloader.py new file mode 100644 index 000000000..4e7e74e6c --- /dev/null +++ b/iqpilot/selfdrive/iqmodeld/tests/test_model_bundle_downloader.py @@ -0,0 +1,133 @@ +""" +Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/ +""" +import hashlib +import http.server +import os +import threading + +import pytest + +from iqpilot.selfdrive.iqmodeld import model_bundle_downloader as dl + + +class _RangeHandler(http.server.BaseHTTPRequestHandler): + store: dict[str, bytes] = {} + cut_first: dict[str, int] = {} + hits: list[tuple[str, str | None]] = [] + + def log_message(self, *a): + pass + + def do_GET(self): + oid = self.path.rsplit("/", 1)[-1] + data = self.store[oid] + rng = self.headers.get("Range") + self.hits.append((oid, rng)) + start = int(rng.split("=")[1].rstrip("-")) if rng else 0 + body = data[start:] + cut = self.cut_first.pop(oid, None) + if cut is not None: + body = body[:cut] + self.send_response(206 if rng else 200) + self.send_header("Content-Length", str(len(body))) + if rng: + self.send_header("Content-Range", f"bytes {start}-{start + len(body) - 1}/{len(data)}") + self.end_headers() + self.wfile.write(body) + + +@pytest.fixture +def server(): + srv = http.server.ThreadingHTTPServer(("127.0.0.1", 0), _RangeHandler) + t = threading.Thread(target=srv.serve_forever, daemon=True) + t.start() + yield srv + srv.shutdown() + srv.server_close() + + +def _objects(parts): + return [{"oid": hashlib.sha256(p).hexdigest(), "size": len(p)} for p in parts] + + +def test_resume_continues_a_cut_part_and_reuses_finished_parts(server, tmp_path, monkeypatch): + parts = [os.urandom(300_000), os.urandom(300_000), os.urandom(120_000)] + objs = _objects(parts) + _RangeHandler.store = {o["oid"]: p for o, p in zip(objs, parts, strict=True)} + _RangeHandler.hits = [] + _RangeHandler.cut_first = {objs[1]["oid"]: 100_000} + port = server.server_address[1] + monkeypatch.setattr(dl, "_requests_auth", lambda: None) + monkeypatch.setattr(dl, "_resolve_oid", lambda session, base, oid, size, auth: (f"http://127.0.0.1:{port}/o/{oid}", {})) + monkeypatch.setattr(dl, "MODELS_BASE_URLS", ("http://unused",)) + monkeypatch.setattr(dl, "STREAM_RETRIES", 3) + monkeypatch.setattr(dl, "CHUNK", 64 * 1024) + whole = b"".join(parts) + dst = str(tmp_path / "model.pkl") + out = dl.download_lfs_bundle(objs, dst, hashlib.sha256(whole).hexdigest(), len(whole)) + with open(dst, "rb") as f: + assert out == dst and f.read() == whole + assert not os.path.exists(dst + ".parts") + ranges = [r for o, r in _RangeHandler.hits if o == objs[1]["oid"]] + assert ranges[0] is None and ranges[1] == "bytes=100000-" + assert sum(1 for o, _ in _RangeHandler.hits if o == objs[0]["oid"]) == 1 + + +def test_corrupt_finished_part_is_refetched(server, tmp_path, monkeypatch): + parts = [os.urandom(200_000), os.urandom(50_000)] + objs = _objects(parts) + _RangeHandler.store = {o["oid"]: p for o, p in zip(objs, parts, strict=True)} + _RangeHandler.hits = [] + _RangeHandler.cut_first = {} + port = server.server_address[1] + monkeypatch.setattr(dl, "_requests_auth", lambda: None) + monkeypatch.setattr(dl, "_resolve_oid", lambda session, base, oid, size, auth: (f"http://127.0.0.1:{port}/o/{oid}", {})) + monkeypatch.setattr(dl, "MODELS_BASE_URLS", ("http://unused",)) + dst = str(tmp_path / "model.pkl") + os.makedirs(dst + ".parts") + with open(dl._part_path(dst, objs[0]["oid"]), "wb") as f: + f.write(os.urandom(200_000)) + whole = b"".join(parts) + dl.download_lfs_bundle(objs, dst, hashlib.sha256(whole).hexdigest(), len(whole)) + with open(dst, "rb") as f: + assert f.read() == whole + + +def test_hf_single_file_resumes_after_cut(server, tmp_path, monkeypatch): + data = os.urandom(700_000) + oid = hashlib.sha256(data).hexdigest() + _RangeHandler.store = {oid: data} + _RangeHandler.hits = [] + _RangeHandler.cut_first = {oid: 250_000} + port = server.server_address[1] + monkeypatch.setattr(dl, "_hf", lambda: ({"Authorization": "Bearer test"}, lambda p: f"http://127.0.0.1:{port}/o/{oid}")) + monkeypatch.setattr(dl, "STREAM_RETRIES", 3) + monkeypatch.setattr(dl, "CHUNK", 64 * 1024) + dst = str(tmp_path / "policy.pkl") + out = dl.download_hf_file("egpu/policy/x.pkl", dst, oid, len(data)) + with open(dst, "rb") as f: + assert out == dst and f.read() == data + ranges = [r for o, r in _RangeHandler.hits if o == oid] + assert ranges[0] is None and ranges[1] == "bytes=250000-" + assert not os.path.exists(dst + ".hfpart") + + +def test_download_onnx_prefers_hf_then_falls_back(tmp_path, monkeypatch): + from iqpilot.selfdrive.iqmodeld import egpu_helpers as eh + meta = {"key": "m", "sha256": "ab" * 32, "download": {"kind": "comma_lfs", "size": 5}} + monkeypatch.setattr(eh, "onnx_cache_path", lambda m: str(tmp_path / "m.onnx")) + monkeypatch.setattr("iqpilot.selfdrive.iqmodeld.egpu_model.download_descriptor", lambda m: ("commalfs:" + m["sha256"], 5), raising=False) + calls = [] + import iqpilot.selfdrive.iqmodeld.model_bundle_downloader as dlm + monkeypatch.setattr(dlm, "download_hf_file", lambda path, dst, sha, size, progress_cb=None: (calls.append(("hf", path)), open(dst, "wb").close(), dst)[2]) + monkeypatch.setattr(eh, "resolve_download_url", lambda *a, **k: (calls.append(("lfs",)), "http://unused")[1]) + out = eh.download_onnx(meta) + assert calls == [("hf", "onnx/" + "ab" * 32 + ".onnx")] and out == str(tmp_path / "m.onnx") + calls.clear() + def boom(*a, **k): + calls.append(("hf-fail",)); raise RuntimeError("hf down") + monkeypatch.setattr(dlm, "download_hf_file", boom) + with pytest.raises(Exception): + eh.download_onnx(meta) + assert calls[:2] == [("hf-fail",), ("lfs",)] diff --git a/iqpilot/selfdrive/iqmodeld/tools/compile_egpu_model.py b/iqpilot/selfdrive/iqmodeld/tools/compile_egpu_model.py index 1d2bc9846..8abc3b9b8 100644 --- a/iqpilot/selfdrive/iqmodeld/tools/compile_egpu_model.py +++ b/iqpilot/selfdrive/iqmodeld/tools/compile_egpu_model.py @@ -7,14 +7,20 @@ import argparse import gc import os import pickle +import sys import time -os.environ.setdefault("DEV", "USB+AMD:LLVM") os.environ.setdefault("FLOAT16", "1") os.environ.setdefault("JIT_BATCH_SIZE", "0") os.environ.setdefault("GMMU", "0") os.environ.setdefault("TC_OPT", "2") +HOST = "--host" in sys.argv +if HOST: + from iqpilot.selfdrive.iqmodeld.tools.egpu_host_mock import DEFAULT_ARCH, activate + activate(sys.argv[sys.argv.index("--arch") + 1] if "--arch" in sys.argv else DEFAULT_ARCH) +os.environ.setdefault("DEV", "USB+AMD:LLVM") + import numpy as np from iqpilot.selfdrive.iqmodeld.egpu_helpers import egpu_pkl_path, local_onnx, patch_tinygrad_fetch_fw @@ -150,7 +156,7 @@ def compile_policy_model(meta: dict, onnx_path: str, out_path: str) -> str: from tinygrad.engine.jit import TinyJit from tinygrad.nn.onnx import OnnxRunner - from iqpilot.selfdrive.iqmodeld.egpu_policy import POLICY_FORMAT, PackedInputs, make_queues, make_run_policy + from iqpilot.selfdrive.iqmodeld.egpu_policy import POLICY_FORMAT, PackedInputs, dump_oob, load_bundle, make_queues, make_run_policy if meta.get("split"): raise RuntimeError(f"model {meta['key']} is a split model; eGPU compiles fused models only") @@ -176,7 +182,7 @@ def compile_policy_model(meta: dict, onnx_path: str, out_path: str) -> str: baseline = step(SEED + i) if baseline.shape[0] != meta["output_len"]: raise RuntimeError(f"model output length {baseline.shape[0]} != registry {meta['output_len']}") - if not np.isfinite(baseline).all(): + if not HOST and not np.isfinite(baseline).all(): raise RuntimeError("compiled policy produced non-finite outputs") bundle = { @@ -191,16 +197,15 @@ def compile_policy_model(meta: dict, onnx_path: str, out_path: str) -> str: } os.makedirs(os.path.dirname(out_path), exist_ok=True) tmp = out_path + ".part" - print("serialize") + print("serialize (out-of-band buffers)") with open(tmp, "wb") as f: - pickle.dump(bundle, f, protocol=pickle.HIGHEST_PROTOCOL) + dump_oob(bundle, f) del bundle, jit, queues, packed gc.collect() print("reload + validate") - with open(tmp, "rb") as f: - jit = pickle.load(f)["run_policy"] + jit = load_bundle(tmp)["run_policy"] queues = make_queues(input_spec, frame_skip, device) packed = PackedInputs(input_spec) outs = [] @@ -211,6 +216,9 @@ def compile_policy_model(meta: dict, onnx_path: str, out_path: str) -> str: flat = out.numpy().reshape(-1) packed.views["prev_feat"][:] = flat[meta["output_slices"]["hidden_state"]].reshape(packed.views["prev_feat"].shape) outs.append(flat) + if HOST: + os.replace(tmp, out_path) + return out_path if not np.array_equal(outs[-1], baseline): raise RuntimeError("policy outputs differ from baseline after pickle round trip") if np.array_equal(outs[0], outs[-1]): @@ -234,7 +242,11 @@ def main() -> None: p.add_argument("--progress-base", type=float, default=None) p.add_argument("--progress-span", type=float, default=0.0) p.add_argument("--format", type=int, default=2, choices=(1, 2)) + p.add_argument("--host", action="store_true", help="compile on a mock dock (no AMD hardware); outputs need a dock parity gate") + p.add_argument("--arch", default=None, help="target gfx arch for --host") args = p.parse_args() + if args.host and args.format != 2: + raise SystemExit("--host supports format 2 only") if args.model is not None: if args.model in EGPU_MODELS: diff --git a/iqpilot/selfdrive/iqmodeld/tools/convert_egpu_oob.py b/iqpilot/selfdrive/iqmodeld/tools/convert_egpu_oob.py new file mode 100644 index 000000000..3efa0b08e --- /dev/null +++ b/iqpilot/selfdrive/iqmodeld/tools/convert_egpu_oob.py @@ -0,0 +1,44 @@ +""" +Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/ +""" +from __future__ import annotations + +import argparse +import gc +import os + +os.environ.setdefault("DEV", "USB+AMD:LLVM") +os.environ.setdefault("GMMU", "0") + +from iqpilot.selfdrive.iqmodeld.egpu_helpers import patch_tinygrad_fetch_fw +from iqpilot.selfdrive.iqmodeld.egpu_policy import dump_oob, is_oob, load_bundle + + +def convert(src: str, dst: str) -> str: + patch_tinygrad_fetch_fw() + if is_oob(src): + if src != dst: + os.replace(src, dst) + return dst + bundle = load_bundle(src) + tmp = dst + ".part" + with open(tmp, "wb") as f: + dump_oob(bundle, f) + del bundle + gc.collect() + load_bundle(tmp) + os.replace(tmp, dst) + return dst + + +def main() -> None: + p = argparse.ArgumentParser() + p.add_argument("src") + p.add_argument("--out", default=None) + args = p.parse_args() + out = convert(args.src, args.out or args.src) + print(f"converted -> {out} ({os.path.getsize(out) / 1e6:.1f} MB)") + + +if __name__ == "__main__": + main() diff --git a/iqpilot/selfdrive/iqmodeld/tools/egpu_host_mock.py b/iqpilot/selfdrive/iqmodeld/tools/egpu_host_mock.py new file mode 100644 index 000000000..b01d7c2a0 --- /dev/null +++ b/iqpilot/selfdrive/iqmodeld/tools/egpu_host_mock.py @@ -0,0 +1,60 @@ +""" +Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/ +""" +from __future__ import annotations + +import os +import sys +import tempfile + +DEFAULT_ARCH = "gfx1200" +MOCK_DEV = "MOCKUSB+AMD:LLVM" + + +def tinygrad_tree() -> str: + override = os.environ.get("IQ_TINYGRAD_TREE") + if override: + return override + here = os.path.dirname(os.path.abspath(__file__)) + root = os.path.abspath(os.path.join(here, "..", "..", "..", "..")) + return os.path.join(root, "components", "tinygrad") + + +def activate(arch: str = DEFAULT_ARCH, execute: bool = False) -> None: + assert "tinygrad" not in sys.modules, "egpu_host_mock.activate must run before tinygrad is imported" + os.environ["DEV"] = f"{MOCK_DEV}:{arch}" + tree = tinygrad_tree() + if tree not in sys.path: + sys.path.insert(0, tree) + from tinygrad.runtime.autogen import libc + if sys.platform == "darwin": + # A Homebrew-LLVM gfx1200 kernel (no s_code_end padding) hung a real dock; ship only container-built artifacts. + print("egpu_host_mock: native macOS LLVM output is for tests only; use scripts/iqpilot/host_egpu_compile_docker.sh for artifacts", + file=sys.stderr) + + def memfd_create(name, flags): + fd, path = tempfile.mkstemp(prefix=b"iq_mock_" + bytes(name) + b"_") + os.unlink(path) + return fd + libc.memfd_create = memfd_create + if not hasattr(libc, "MFD_CLOEXEC"): + libc.MFD_CLOEXEC = 1 + if not execute: + import ctypes + from test.mockgpu.amd import amdgpu + amdgpu.remu.run_asm = lambda *args, **kwargs: 0 + pm4_wait = amdgpu.PM4Executor._exec_wait_reg_mem + sdma_poll = amdgpu.SDMAExecutor._execute_poll_regmem + + # Without kernel execution no memory wait carries information; a blocked wait would need a host write to re-poll it. + def pm4_wait_passthrough(self, n): + if not pm4_wait(self, n): + self.rptr[0] += 7 + return True + + def sdma_poll_passthrough(self): + if not sdma_poll(self): + self.rptr[0] += ctypes.sizeof(amdgpu.sdma_pkts.poll_regmem) + return True + amdgpu.PM4Executor._exec_wait_reg_mem = pm4_wait_passthrough + amdgpu.SDMAExecutor._execute_poll_regmem = sdma_poll_passthrough diff --git a/iqpilot/selfdrive/iqmodeld/tools/oob_rewrite.py b/iqpilot/selfdrive/iqmodeld/tools/oob_rewrite.py new file mode 100644 index 000000000..3f109669f --- /dev/null +++ b/iqpilot/selfdrive/iqmodeld/tools/oob_rewrite.py @@ -0,0 +1,64 @@ +""" +Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/ +""" +from __future__ import annotations + +import argparse +import os +import pickletools +import struct + +from iqpilot.selfdrive.iqmodeld.egpu_policy import OOB_MAGIC + +MIN_OOB_BYTES = 1 << 16 +NEXT_BUFFER = b"\x97" +READONLY_BUFFER = b"\x98" + + +def rewrite_oob(src: str, dst: str, min_bytes: int = MIN_OOB_BYTES) -> tuple[int, int]: + # tinygrad pickles device buffers as PickleBuffers, which land in-band as BYTEARRAY8/BINBYTES8 + # without a buffer_callback; moving those opcodes out-of-band is byte-for-byte what a protocol-5 + # dump with a buffer_callback produces, so nothing has to be unpickled (no dock needed). + with open(src, "rb") as f: + data = f.read() + ops = list(pickletools.genops(data)) + proto = next((arg for op, arg, _ in ops if op.name == "PROTO"), 0) + if proto < 5: + raise ValueError(f"{src} is pickle protocol {proto}; out-of-band buffers need protocol 5") + moved = 0 + tmp = dst + ".part" + with open(tmp, "wb") as out, open(tmp + ".buf", "wb") as bufs: + ops_stream = bytearray() + for i, (op, arg, pos) in enumerate(ops): + end = ops[i + 1][2] if i + 1 < len(ops) else len(data) + if op.name in ("BYTEARRAY8", "BINBYTES8", "BINBYTES") and len(arg) >= min_bytes: + ops_stream += NEXT_BUFFER + if op.name != "BYTEARRAY8": + ops_stream += READONLY_BUFFER + bufs.write(struct.pack(" None: + p = argparse.ArgumentParser() + p.add_argument("src") + p.add_argument("dst") + args = p.parse_args() + moved, total = rewrite_oob(args.src, args.dst) + print(f"{args.dst}: moved {moved} buffers out-of-band ({total} opcodes, {os.path.getsize(args.dst) / 1e6:.1f} MB)") + + +if __name__ == "__main__": + main() diff --git a/iqpilot/selfdrive/selfdrived/events.py b/iqpilot/selfdrive/selfdrived/events.py index 60a37668e..f296a9744 100755 --- a/iqpilot/selfdrive/selfdrived/events.py +++ b/iqpilot/selfdrive/selfdrived/events.py @@ -375,7 +375,7 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { "Pay Attention", "", AlertStatus.normal, AlertSize.small, - Priority.LOW, VisualAlert.none, AudibleAlert.none, .1), + Priority.MID, VisualAlert.none, AudibleAlert.none, .1), }, EventName.promptDriverDistracted: { @@ -901,7 +901,7 @@ if HARDWARE.get_device_type() == 'mici': "Pay Attention", "", AlertStatus.normal, AlertSize.small, - Priority.LOW, VisualAlert.none, AudibleAlert.none, 2), + Priority.MID, VisualAlert.none, AudibleAlert.none, 2), }, EventName.promptDriverDistracted: { ET.PERMANENT: Alert( diff --git a/iqpilot/selfdrive/selfdrived/iq_events.py b/iqpilot/selfdrive/selfdrived/iq_events.py index e9073f689..89a8aec67 100644 --- a/iqpilot/selfdrive/selfdrived/iq_events.py +++ b/iqpilot/selfdrive/selfdrived/iq_events.py @@ -1,5 +1,6 @@ import iqpilot.cereal.messaging as messaging from iqpilot.cereal import log, car, custom +from iqpilot.common.params import Params from iqpilot.common.constants import CV from iqpilot.common.atlas_alerts import EventBook as EventsBase, Tier as Priority, Tags as ET, AlertCard as Alert, \ NoEntryCard as NoEntryAlert, HardDisableCard as ImmediateDisableAlert, ChimeCard as EngagementAlert, \ @@ -94,6 +95,24 @@ _CAMERA_LABELS = { } _POLICE_CHIMED_IDS: set[str] = set() +_USA_REGION_CODES = frozenset(("US", "USA", "UNITED STATES", "UNITED STATES OF AMERICA")) + + +def _configured_country_code() -> str: + try: + value = Params().get("OsmLocationName") + except Exception: + return "" + if isinstance(value, bytes): + value = value.decode("utf-8", "ignore") + return str(value or "").strip().upper() + + +def _alpr_alert_labels(country_code: str) -> tuple[str, str]: + is_row = bool(country_code) and country_code not in _USA_REGION_CODES + if is_row: + return "Traffic / ALPR Camera", "Traffic / ALPR Camera Detected" + return "Flock / ALPR Camera", "Flock Camera Detected" def speed_camera_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert: @@ -101,14 +120,17 @@ def speed_camera_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMas ctype = int(getattr(nav.cameraType, "raw", nav.cameraType)) label = _CAMERA_LABELS.get(ctype, "Speed Camera") distance = float(nav.cameraDistance) + alpr_detected_label = "Flock Camera Detected" + if ctype == int(custom.IQNavState.CameraType.alpr): + label, alpr_detected_label = _alpr_alert_labels(_configured_country_code()) # RF (BLE/WiFi) Flock detection is a live proximity hit with no meaningful # distance — flockd/navd flag it with distance 0 on the alpr camera type. if ctype == int(custom.IQNavState.CameraType.alpr) and distance <= 0.0: return Alert( - "Flock Camera Detected", + alpr_detected_label, "", AlertStatus.normal, AlertSize.small, - Priority.HIGH, VisualAlert.none, AudibleAlert.prompt, .2) + Priority.LOW, VisualAlert.none, AudibleAlert.prompt, .2) if metric: dist_str = f"{distance:.0f} m" if distance < 1000.0 else f"{distance / 1000.0:.1f} km" else: @@ -134,7 +156,8 @@ def speed_camera_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMas f"{label} • {detail}", "", AlertStatus.normal, AlertSize.small, - Priority.HIGH, VisualAlert.none, audible, .2) + Priority.LOW if ctype == int(custom.IQNavState.CameraType.alpr) else Priority.HIGH, + VisualAlert.none, audible, .2) class IQEvents(EventsBase): diff --git a/iqpilot/selfdrive/selfdrived/tests/test_iq_camera_alerts.py b/iqpilot/selfdrive/selfdrived/tests/test_iq_camera_alerts.py index 2c8378002..42f6b149c 100644 --- a/iqpilot/selfdrive/selfdrived/tests/test_iq_camera_alerts.py +++ b/iqpilot/selfdrive/selfdrived/tests/test_iq_camera_alerts.py @@ -1,13 +1,17 @@ +import copy from types import SimpleNamespace -from iqpilot.cereal import car, custom +from iqpilot.cereal import car, custom, log +from iqpilot.common.atlas_alerts import HardDisableCard, Tags as ET, Tier as Priority +from iqpilot.selfdrive.selfdrived.alertmanager import AlertManager +from iqpilot.selfdrive.selfdrived.events import EVENTS from iqpilot.selfdrive.selfdrived import iq_events -def alert(camera_type, *, report_id="", chime=False): +def alert(camera_type, *, report_id="", chime=False, distance=300.0): nav = SimpleNamespace( cameraType=camera_type, - cameraDistance=300.0, + cameraDistance=distance, cameraSpeedLimit=25.0, cameraAlertId=report_id, cameraChime=chime, @@ -31,3 +35,45 @@ def test_police_chime_is_deduplicated_by_report(): second = alert(custom.IQNavState.CameraType.police, report_id="police-a", chime=True) assert first.audible_alert == car.CarControl.HUDControl.AudibleAlert.prompt assert second.audible_alert == car.CarControl.HUDControl.AudibleAlert.none + + +def test_alpr_wording_uses_configured_region(monkeypatch): + monkeypatch.setattr(iq_events, "_configured_country_code", lambda: "US") + assert alert(custom.IQNavState.CameraType.alpr).alert_text_1.startswith("Flock / ALPR Camera") + assert alert(custom.IQNavState.CameraType.alpr, distance=0.0).alert_text_1 == "Flock Camera Detected" + + monkeypatch.setattr(iq_events, "_configured_country_code", lambda: "DE") + assert alert(custom.IQNavState.CameraType.alpr).alert_text_1.startswith("Traffic / ALPR Camera") + assert alert(custom.IQNavState.CameraType.alpr, distance=0.0).alert_text_1 == "Traffic / ALPR Camera Detected" + + +def test_missing_region_is_safe_and_preserves_flock_wording(monkeypatch): + class UnavailableParams: + def get(self, key): + raise OSError(key) + + monkeypatch.setattr(iq_events, "Params", UnavailableParams) + assert iq_events._configured_country_code() == "" + assert alert(custom.IQNavState.CameraType.alpr, distance=0.0).alert_text_1 == "Flock Camera Detected" + + +def test_driver_attention_and_takeover_alerts_preempt_alpr(): + flock = alert(custom.IQNavState.CameraType.alpr, distance=0.0) + pre_attention = copy.copy(EVENTS[log.OnroadEvent.EventName.preDriverDistracted][ET.PERMANENT]) + prompt_attention = copy.copy(EVENTS[log.OnroadEvent.EventName.promptDriverDistracted][ET.PERMANENT]) + takeover = copy.copy(EVENTS[log.OnroadEvent.EventName.driverDistracted][ET.PERMANENT]) + immediate_disable = HardDisableCard("Regression Test") + + assert flock.priority == Priority.LOW + assert pre_attention.priority == flock.priority + 1 + assert prompt_attention.priority == flock.priority + 1 + assert takeover.priority > flock.priority + assert immediate_disable.priority > flock.priority + + for expected in (pre_attention, prompt_attention, takeover, immediate_disable): + manager = AlertManager() + flock.alert_type = "flock/warning" + expected.alert_type = f"expected/{expected.alert_text_1}" + manager.add_many(0, [flock, expected]) + manager.process_alerts(0, set()) + assert manager.current_alert is expected diff --git a/iqpilot/system/manager/process_config.py b/iqpilot/system/manager/process_config.py index 449fa70e5..77b650722 100644 --- a/iqpilot/system/manager/process_config.py +++ b/iqpilot/system/manager/process_config.py @@ -222,6 +222,8 @@ procs += [ and_(only_onroad, and_(is_tinygrad_model, emac_enabled)), restart_if_crash=True), PythonProcess("iqegpumodeld", "iqpilot.selfdrive.iqmodeld.iqegpumodeld", and_(only_onroad, and_(is_tinygrad_model, egpu_enabled)), restart_if_crash=True), + PythonProcess("egpu_prefetch", "iqpilot.selfdrive.iqmodeld.egpu_prefetch", + and_(only_offroad, and_(is_tinygrad_model, egpu_enabled)), restart_if_crash=True), BundleProcess("backup_manager_k3", "iqpilot_hephaestusd_private", "iqpilot_private.konn3kt.backups.backup_orchestrator", and_(only_offroad, hephaestus_ready_shim, not_low_power)),