IQ.Pilot Release Commit @ 461be14

This commit is contained in:
IQ.Lvbs CI [bot]
2026-08-30 02:58:32 -05:00
parent 99807e037f
commit d42df282ae
63 changed files with 1259 additions and 176 deletions

View File

@@ -196,8 +196,8 @@
}, },
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/egpu_helpers.py": { "python/_iqclosure/iqpilot/selfdrive/iqmodeld/egpu_helpers.py": {
"mode": 420, "mode": 420,
"sha256": "4df0ac0a3f720ee01a70f82908e582682e84848023592a72bd45f2ca74c9b161", "sha256": "0b8f118dc45ad0c3809855800960b47c893ee121ea75d94e8569dcce46d58ce5",
"size": 5535 "size": 7312
}, },
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/egpu_model.py": { "python/_iqclosure/iqpilot/selfdrive/iqmodeld/egpu_model.py": {
"mode": 420, "mode": 420,
@@ -221,8 +221,8 @@
}, },
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/model_bundle_downloader.py": { "python/_iqclosure/iqpilot/selfdrive/iqmodeld/model_bundle_downloader.py": {
"mode": 420, "mode": 420,
"sha256": "62c2c2830c7c8819bf42eb54a34db635128a31fbc2bbb71bc95415e5b873a3c0", "sha256": "27aade5852507baf84ed460b9446d0de4df58a4a535b82c301113040b7ba472a",
"size": 3189 "size": 7305
}, },
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/model_channel.py": { "python/_iqclosure/iqpilot/selfdrive/iqmodeld/model_channel.py": {
"mode": 420, "mode": 420,
@@ -391,7 +391,7 @@
}, },
"python/_iqclosure/iqpilot/selfdrive/selfdrived/events.py": { "python/_iqclosure/iqpilot/selfdrive/selfdrived/events.py": {
"mode": 420, "mode": 420,
"sha256": "e298175d13a99b55e183954d82346fa7adab6b6c161fc72b789f12601d1a9b43", "sha256": "5e37693eeced1e3ba81f03234b7b0dad33b7564826d232d288998be5fae7efd6",
"size": 37038 "size": 37038
}, },
"python/_iqclosure/iqpilot/selfdrive/state_estimation/__init__.py": { "python/_iqclosure/iqpilot/selfdrive/state_estimation/__init__.py": {
@@ -531,22 +531,22 @@
}, },
"python/iqpilot_private/emac/bulk_transport.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/emac/bulk_transport.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "3f55e4c621ea86c0a6ac9ecac091fa90491048df5fc5bf09cbb668f0c0b71051", "sha256": "cc7a4087336116187655ea52bc48391e447b81529c2b923e60bf886bc3c7f58a",
"size": 334016 "size": 334016
}, },
"python/iqpilot_private/emac/mac_client.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/emac/mac_client.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "e748eba2eec567e5f0c9e1dce60ef71795d2a99f58238ad89495dcee7672c70c", "sha256": "9d48157df0d78be709cf3722743b6137cbf73eb53c315d7307e6a214ae140664",
"size": 267952 "size": 267952
}, },
"python/iqpilot_private/emac/mac_protocol.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/emac/mac_protocol.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "46f79e0fe9d799003b49e5fe11969a1716974a9c6d20d52488d79ef8b7137a5a", "sha256": "9b7dc1997e780198c2532045e41236dd342b4dc4d3cd31b06be27246121d6ab7",
"size": 203544 "size": 203544
}, },
"python/iqpilot_private/emac/maciqmodeld.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/emac/maciqmodeld.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "b7acc1edcf50dd2e9c2d88f2f4824d0a28b0def3a57b144a340ddca1bdd14254", "sha256": "ec1f1f0ce541abf00021757fa25c4de54c0037d36eb0d194bdf4ad6c77c778e6",
"size": 333336 "size": 333336
}, },
"runtime": { "runtime": {
@@ -558,9 +558,9 @@
} }
}, },
"signatures": { "signatures": {
"python/iqpilot_private/emac/bulk_transport.cpython-312-aarch64-linux-gnu.so": "zdchbAhGhL0eeX6qgQUw0CwrPGIg896Sv/0AG0hHrJ/1Ek7KnB4llxmWUA/e4HB4hlCqTEMO416N3csIfDikBA==", "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": "p0ukaERkDTTF6vVVn0TK1ImrPMArUWmqAY2kJnctlb38486SIeFZxC4gCP0cU9++j7i7ydmXNOcUqxG7qIPECQ==", "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": "5rLpvBoiOP+/N/zblRnXcltpOtULO3uZx2bQYeCM/A8x8m9HLhIBUMJzSVIYj5VcbvgHmzc911UE18RrwEmyCg==", "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": "otKpewDX8CPqirHPXn+mTht7oi/AgkQiZ2VXPZM3/wryLSvzRKJzMyf5nXZ/lb0Z8J8vsFKUkdVhxKgYkeLIDw==" "python/iqpilot_private/emac/maciqmodeld.cpython-312-aarch64-linux-gnu.so": "MJXrXlkDjgdUeNxck8kh4SeLCBRLUaalY3vJ8nOAOGFCwGFE1UXrw+wBl6HaBNu86v0MlBrT1cN89GkNtdZUDg=="
} }
} }

View File

@@ -1 +1 @@
9uZxIFB8JGkbVFGkwQJ+7gxvqL+EJf7PS0DqSsFYuiaw39lYS4qp5rzPOmn0ixSDSYlzsEwnwq5ojMdbX/ZJCQ== ViYlJji0FjCcbz07ouwB45Lnj1xSE5DytMonAUAg0KskmG9gyid8fEGvrHNZxQDPRD4EpL0N9oIasaFTKFWcDA==

View File

@@ -6,12 +6,16 @@ from __future__ import annotations
import hashlib import hashlib
import json import json
import os import os
from iqpilot.common.swaglog import cloudlog
import urllib.request import urllib.request
from pathlib import Path from pathlib import Path
from iqpilot.system.hardware.usb import egpu_dock_ready from iqpilot.system.hardware.usb import egpu_dock_ready
USB_SYSFS_ROOT = "/sys/bus/usb/devices" 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" 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") 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: def onnx_cache_path(meta: dict) -> str:
from iqpilot.system.hardware.hw import Paths from iqpilot.system.hardware.hw import Paths
return os.path.join(Paths.model_root(), f"{meta['model_name']}_{meta['sha256'][:8]}.onnx") 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: if not download_url:
raise RuntimeError(f"model {meta['key']} has no download source; stage the onnx at {onnx_cache_path(meta)}") 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) path = onnx_cache_path(meta)
os.makedirs(os.path.dirname(path), exist_ok=True) 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" tmp = path + ".part"
digest = hashlib.sha256() digest = hashlib.sha256()
got = 0 got = 0
@@ -142,12 +156,20 @@ def download_onnx(meta: dict, progress_cb=None) -> str:
return path return path
def download_precompiled(meta: dict, progress_cb=None, policy: bool = False) -> str | None: def download_precompiled(meta: dict, progress_cb=None, policy: bool = False, oob: bool = False) -> str | None:
art = meta.get("egpu_policy_artifact" if policy else "egpu_artifact") field = "egpu_oob_artifact" if oob else "egpu_policy_artifact" if policy else "egpu_artifact"
if not art or not art.get("objects"): art = meta.get(field)
if not art or not (art.get("objects") or art.get("hf_path")):
return None return None
from iqpilot.selfdrive.iqmodeld.model_bundle_downloader import download_lfs_bundle from iqpilot.selfdrive.iqmodeld.model_bundle_downloader import download_hf_file, download_lfs_bundle
dest = egpu_policy_pkl_path(meta) if policy else egpu_pkl_path(meta) 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) 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 _orig = helpers.fetch_fw
def fetch_fw(path, name, sha256): 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") p = pathlib.Path(f"/lib/firmware/{path}/{name}.zst")
if p.is_file(): if p.is_file():
blob = zstandard.ZstdDecompressor().stream_reader(p.read_bytes()).read() blob = zstandard.ZstdDecompressor().stream_reader(p.read_bytes()).read()
if hashlib.sha256(blob).hexdigest() == sha256: if hashlib.sha256(blob).hexdigest() == sha256:
return blob 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 fetch_fw._iq_patched = True
helpers.fetch_fw = fetch_fw helpers.fetch_fw = fetch_fw

View File

@@ -27,6 +27,63 @@ def _requests_auth():
return None 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: def _lfs_endpoint(base_url: str) -> str:
return base_url.split("/raw/", 1)[0] + ".git/info/lfs" 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", {}) 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: def download_lfs_bundle(objects: list, dst: str, sha256: str, size: int, progress_cb=None) -> str:
import requests import requests
auth = _requests_auth() auth = _requests_auth()
session = requests.Session() session = requests.Session()
os.makedirs(os.path.dirname(dst), exist_ok=True) os.makedirs(dst + ".parts", exist_ok=True)
tmp = dst + ".part"
total = int(size) or sum(int(o["size"]) for o in objects) 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 last_error: Exception | None = None
for base_url in MODELS_BASE_URLS: for base_url in MODELS_BASE_URLS:
for attempt in range(STREAM_RETRIES): for _attempt in range(STREAM_RETRIES):
try: try:
digest = hashlib.sha256()
got = 0
with open(tmp, "wb") as f:
for obj in objects: for obj in objects:
href, headers = _resolve_oid(session, base_url, obj["oid"], int(obj["size"]), auth) path = _part_path(dst, obj["oid"])
obj_auth = None if headers.get("Authorization") else auth if _part_complete(path, obj["oid"], int(obj["size"])):
with session.get(href, headers=headers, stream=True, timeout=120, auth=obj_auth) as r: continue
r.raise_for_status() got[0] = done_bytes
for chunk in r.iter_content(CHUNK): _fetch_part(session, base_url, obj, path, auth, progress)
f.write(chunk) if not _part_complete(path, obj["oid"], int(obj["size"])):
digest.update(chunk) if os.path.getsize(path) >= int(obj["size"]):
got += len(chunk) os.remove(path)
if progress_cb is not None and total: raise RuntimeError(f"part {obj['oid'][:12]} incomplete or failed verification")
progress_cb(min(1.0, got / total)) done_bytes += int(obj["size"])
if total and got != total: got[0] = done_bytes
raise RuntimeError(f"size mismatch: {got}/{total} bytes") break
if sha256 and digest.hexdigest() != sha256:
raise RuntimeError("sha256 mismatch")
os.replace(tmp, dst)
return dst
except Exception as e: except Exception as e:
last_error = e last_error = e
try: 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) 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: except OSError:
pass pass
raise RuntimeError(f"model bundle download failed: {last_error}") 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

View File

@@ -375,7 +375,7 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
"Pay Attention", "Pay Attention",
"", "",
AlertStatus.normal, AlertSize.small, AlertStatus.normal, AlertSize.small,
Priority.LOW, VisualAlert.none, AudibleAlert.none, .1), Priority.MID, VisualAlert.none, AudibleAlert.none, .1),
}, },
EventName.promptDriverDistracted: { EventName.promptDriverDistracted: {
@@ -901,7 +901,7 @@ if HARDWARE.get_device_type() == 'mici':
"Pay Attention", "Pay Attention",
"", "",
AlertStatus.normal, AlertSize.small, AlertStatus.normal, AlertSize.small,
Priority.LOW, VisualAlert.none, AudibleAlert.none, 2), Priority.MID, VisualAlert.none, AudibleAlert.none, 2),
}, },
EventName.promptDriverDistracted: { EventName.promptDriverDistracted: {
ET.PERMANENT: Alert( ET.PERMANENT: Alert(

View File

@@ -176,7 +176,7 @@
}, },
"python/iqpilot_private/models/__init__.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/models/__init__.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "c6ff7efa8f20decbd9ee75a2b11d6be6b2b0888b45d90dce65ac3a1615e3afc5", "sha256": "aec72c78d58c37afb1b57bea4a29d322032fffdfa59deabdc85b4322b5ca3787",
"size": 67616 "size": 67616
}, },
"python/iqpilot_private/models/__init__.py": { "python/iqpilot_private/models/__init__.py": {
@@ -186,37 +186,37 @@
}, },
"python/iqpilot_private/models/big_catalog.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/models/big_catalog.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "60bb1b539739e75249c3034ff505aeb9bf38d6fcf8d7f713d61395104bee0002", "sha256": "30a25ebd3b37e2307c3a80ae0fea70b314ab71427d313ba5e2d540c4ba7658ed",
"size": 133840 "size": 133840
}, },
"python/iqpilot_private/models/egpu_model.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/models/egpu_model.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "7111179a66be1ac8bbcefcd2c6f836150e9e31b01d36244315203123f980beb8", "sha256": "e8cb29388e31f636d19f916d5a1d32f38c38113f5fa882344d4effceb6e37d85",
"size": 68112 "size": 68112
}, },
"python/iqpilot_private/models/emac_model_meta.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/models/emac_model_meta.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "16bec7e29dc9b07b743d523735eb3adfd5b43b0ffa7cc89d189ee050065aa08d", "sha256": "b7938d9521352d5c1fa6b0d7ff8f3f11062e33bcc0d9a72ffb0edfb525423b6e",
"size": 68264 "size": 68264
}, },
"python/iqpilot_private/models/fetcher.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/models/fetcher.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "8eaf6bc52ad788587e3a9bfbd06246f68931c8a151772e37c895c396001e5243", "sha256": "dad0c3c3a73338647da25dead17da0db6cebb24527bc9db381cd16753741f676",
"size": 136200 "size": 136200
}, },
"python/iqpilot_private/models/git_auth.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/models/git_auth.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "c785e13bf86fcfdc5bf0d5e5acc6a49b764f800e4a5e87bb740cd187fdeb4584", "sha256": "ce815034dd8bdadb79131e0b51013a9f9772c4421dfb96b62431d84125089ad2",
"size": 67864 "size": 68056
}, },
"python/iqpilot_private/models/helpers.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/models/helpers.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "d98244f8f9b3b1e1f412802d93095a0a05dedf5c6a98c9cefbee6c27e999d341", "sha256": "798843f52120210c19da5b6c98723ca15d782ea9cd445cc8f96009471990d618",
"size": 137944 "size": 137944
}, },
"python/iqpilot_private/models/manager.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/models/manager.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "9df118ff8a45497e258ac4cf520db3c80f74b8cd22a41b54bba26d372bc6a8e5", "sha256": "96be19ef4f59b87e44092c61f70676468617365923c4c8b91cf7e0b88cc62c05",
"size": 340000 "size": 340000
}, },
"runtime": { "runtime": {
@@ -228,13 +228,13 @@
} }
}, },
"signatures": { "signatures": {
"python/iqpilot_private/models/__init__.cpython-312-aarch64-linux-gnu.so": "XBVN/BJBOnlVrPJ0pq/+rAkGwSDkJk3t29YpPaYsWCQPWD0JADTip4a8vj0GvCQgXdGwNsMJb2b7Cu6TeoOOAw==", "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": "zhE70qcpwK9vSvqYp7fKuMptsV2JMMpmsnUeyFuA89mtFDKzUoJi+jxwOsvcH1b0qk6CuemH86l0/UpA0AbRAg==", "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": "4JFNjPPV2i8xPXrWrwpkuOWPDRCD/m0+bVGgWO9TEmFLKbAu+RQslAHesptxfDAyomJbeRLBvXk1KVRaTzQ+CA==", "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": "Q1luLZ5apcCJ3Im8Py3uP0D42VYXdsw5y48FAtofFrCQJlZ+LTPshjSVbu2GYQnxny9fFt4J6DT9zvMtLheqAA==", "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": "YPc7Ix5gW6YCQX2DrpL5bWu5RKMIOCJvaR/xDwTJmo3/dfkgvfAv7jG41rTxpXaRl8rM8ipUoPI8tlKGzucqDw==", "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": "R6WdFIgsLn4/8fq5qSU64ye7n+Ql9tr3Vhy4gYKIGKWY5GkXplkc8KMA1ciO7yy0n6Qw5HPCmGKrAbkQerrfBw==", "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": "BoA4zydMAnpLyAFxzjwvggxB8Hzx+OJd3puAAckzi0UiiGhdiTGl9jexUcGpOolST3pPCRSoCXT0fVihW/OFDw==", "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": "8VccT47QBNee1ZcnKSsHchV10dOJP0q55FtabpSZr+azLP+hmWiClG3j9VqRe5ctAzuMa53m7eSRjXiTKCpwDw==" "python/iqpilot_private/models/manager.cpython-312-aarch64-linux-gnu.so": "+53NsV73jkF/1x9Bzsw99oZRhVBtqlQTeXaPfHydTAssd2V3UMO+BWqagtGbVG5qIOcgjaNweNExGu+Abo7iDA=="
} }
} }

View File

@@ -1 +1 @@
LQYoKAbNbtkF/8GkU70iyR+NZQI0fmM7PWUQ2IphOB0P4nCRjgRCnkkE/g3KS+d4f/R60fgakG135GVCQgsaBw== 8t0c8OQ6AY5JlVXqGQkiF2rlj3kN3MIwGHVz4TOgaudkW7XegnRhUqngcpNo5C74wFDKNGWsvXOJ4WtIiIrfCg==

View File

@@ -1 +1 @@
44200007e6d1ccd1e6016db08abf3f54d733913f 7ef341203873309cd6087fbd6a0d355183f21472

View File

@@ -101,8 +101,8 @@
}, },
"python/_iqclosure/iqpilot/selfdrive/car/cruise.py": { "python/_iqclosure/iqpilot/selfdrive/car/cruise.py": {
"mode": 420, "mode": 420,
"sha256": "6596f1f27b73452f13f89241fb6a2cc11096904bf37c665eccf739430f9ec63a", "sha256": "cb74245bc10dff8db84eeba299ab56915bb399f391f67c552bf53aa52e55841c",
"size": 14710 "size": 16399
}, },
"python/_iqclosure/iqpilot/selfdrive/car/long_increments.py": { "python/_iqclosure/iqpilot/selfdrive/car/long_increments.py": {
"mode": 420, "mode": 420,
@@ -186,7 +186,7 @@
}, },
"python/iqpilot_private/navd/__init__.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/navd/__init__.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "070f9e626498045eaa2420b4155eea666173bf2b9177570bf858bda16c6db653", "sha256": "ab83e566caaae8db57a64209742a9243ccfb332daab10577eef1dd11ff2f062e",
"size": 67616 "size": 67616
}, },
"python/iqpilot_private/navd/__init__.py": { "python/iqpilot_private/navd/__init__.py": {
@@ -194,104 +194,109 @@
"sha256": "3faeaa3363b2f50fa135ad4c1f2466853d684d562120425f77a58039837fd8fb", "sha256": "3faeaa3363b2f50fa135ad4c1f2466853d684d562120425f77a58039837fd8fb",
"size": 69 "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": { "python/iqpilot_private/navd/amap_client.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "9e20c2f70422428de5109e0fa9c5c26ca09b6e684a86f519dac82e539b9d529e", "sha256": "75a0f2dfc9b6b21298f97be0d43079e873333faaaeb904a251247cf9b1958469",
"size": 202040 "size": 202040
}, },
"python/iqpilot_private/navd/event_builder.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/navd/event_builder.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "187419474acc3b1ef8307331c0143f9b1a4375a3f7b031beb8fea4bee9ae6e8c", "sha256": "30ffec950cdaa4aaf49c7e7c1d2a150c98b13941a31067969ec6beffb4b4cc6e",
"size": 136960 "size": 136960
}, },
"python/iqpilot_private/navd/helpers.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/navd/helpers.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "eff4eb0b5f23cbb3af85da0e55a861e82a1d8d3f8be3a0d8c4c8137003637c7e", "sha256": "2a2b1be142b042cf85d008b7bc7605a5fc89e8c80f9303ffd8e7f503092cadbf",
"size": 135960 "size": 135960
}, },
"python/iqpilot_private/navd/incident_feed.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/navd/incident_feed.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "091227540881f177d29d53ccd09eacdde413f672bc15523966fff7d20c055e94", "sha256": "387b407e49fe9eb3debf58de1e80d26e878a55af8d7d386c69fe690fabdfc278",
"size": 135528 "size": 135528
}, },
"python/iqpilot_private/navd/iqmapd.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/navd/iqmapd.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "1bf7f2f0846d4f5c52b12a6388e427a8e477c0d8fd67f88e3fb2186aa8d6a826", "sha256": "4ae88149ae54971836bb0e539d3e6d8c400045d32b2524396ffb2e9bf3cd377e",
"size": 135632 "size": 135632
}, },
"python/iqpilot_private/navd/long_decel.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/navd/long_decel.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "183f2af1da64ba01bf0735ed6d9fb20ced5900a465f2608fb4332d4f4fbb0ba9", "sha256": "4eaa7376674fd8ed11661fe9587b8756cb0816dd7dcf9ee02a7122f4e16c4208",
"size": 69752 "size": 69752
}, },
"python/iqpilot_private/navd/mapbox_client.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/navd/mapbox_client.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "85b6a11d4bebe5b17792d3a2c1d4cf6577284ed3465bc078202bd1b50244478f", "sha256": "bf259ccce678c8450d3a8646d641e3c1cee841ce62106964ddd8f0af76590922",
"size": 68144 "size": 68144
}, },
"python/iqpilot_private/navd/nav_cameras.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/navd/nav_cameras.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "f873227e7a089ca5b3e637f38815f962bf6612d1d89a3d98ad9c2f13303f80b2", "sha256": "0af88978fcc2f04be1d9378eb0702f78d31321d845bf2f5b92efead4a6246b41",
"size": 135600 "size": 203056
}, },
"python/iqpilot_private/navd/nav_turn_signals.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/navd/nav_turn_signals.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "e590e12c1bfda8537fb9333cb87f5f9877355cd491fd4fbee59c7055c98c05e0", "sha256": "6dab10706556491be6b2481f805ee4d016f04d9faaddd8943d384851d14352b7",
"size": 135560 "size": 135560
}, },
"python/iqpilot_private/navd/navd.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/navd/navd.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "f4de1e41111d765e04b0578b777aa6898337df07a19ea3ccd7d84740f33bb10c", "sha256": "4bc9df93ca01b1623ae080ba468916c1ac1d22eb30d0b922bc97cc8d8f4177de",
"size": 797296 "size": 797328
}, },
"python/iqpilot_private/navd/navincidentd.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/navd/navincidentd.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "3d1c7a8479deee9d21ac61e38eccf321a0761fa7a104d85d75e770126063c3c5", "sha256": "213cd32635b223ca3c39ccb5b65b2bbfe050b5401ff8e1285a4fd2f288107df5",
"size": 135344 "size": 135344
}, },
"python/iqpilot_private/navd/navrenderd.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/navd/navrenderd.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "2621b0ee58c5a7db170050c54acbcea92b1bcfd3c5fc07f5342fce16e8e84069", "sha256": "87c4fae3eda6107363d54a32d200b6237ce02aa67db429895f68935476424bd0",
"size": 135584 "size": 135584
}, },
"python/iqpilot_private/navd/osm_cameras.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/navd/osm_cameras.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "6bea816d9ab4cfe8eeebaaa39c3eec8f0d2f2e80d83ef8b685eb8444332d2286", "sha256": "236ff9dcb24f0599f8feab2fcfbf76754816ae3a8c4d9c7692d86c3579c6feca",
"size": 136392 "size": 202384
}, },
"python/iqpilot_private/navd/reroute.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/navd/reroute.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "21a0e0fe1ae366fae40755ddcee1b2d14dd36208d0cd4ea93f6d28a2946433c9", "sha256": "5ef4fad4e686b94b258a4592733451358e0838f9cfe3fa0b2012cc87e4f550da",
"size": 136216 "size": 136216
}, },
"python/iqpilot_private/navd/road_incidents.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/navd/road_incidents.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "007b5062fd915d01f0eb782cb190b47025671ac643a2cc92b7a7eb36ed9f3bd1", "sha256": "9833ba14d5565cfd9be9442a090d90134eb18dd26e2664aaf403cd8fc4e55906",
"size": 203136 "size": 203136
}, },
"python/iqpilot_private/navd/route_manager.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/navd/route_manager.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "1a34010bdf5f58678f8fd198162de2d16d2dae0d1aef91b56ea88c54122080ef", "sha256": "6dc7c0b3600eaaf4429f960c68df3b2f9f50b44c515f3b8395541fe367cdcafc",
"size": 877864 "size": 877864
}, },
"python/iqpilot_private/navd/runtime_common.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/navd/runtime_common.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "7d4ed2bf8c46b2bdb8d9558035301835c45e521cd5ceaee2a04fafdb3d3688ed", "sha256": "df427f9ecd65f070a14b058b1ce3b12a6ff95cee0df4fb0ecbb7d281f6f85f48",
"size": 135760 "size": 135760
}, },
"python/iqpilot_private/navd/traffic.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/navd/traffic.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "289c5ef19885a3b032ea9183ed0f889c16f09050fe2ecf0d8e1e544f31e57283", "sha256": "d1bcf0d4bdf3cf3d1877185b1ffb282ae2f837c181be16f5b2562db40033c5ad",
"size": 69720 "size": 69720
}, },
"python/iqpilot_private/navd/turn_desire.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/navd/turn_desire.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "f3defee1e4aa36f6493cb74f2343a31e3b65dd48dac22517ea9f5c160b76a907", "sha256": "aebe5ee4db658d6383afbf5e2298820f66fb3a3deadf557a7d1557a24bf3189a",
"size": 69768 "size": 69768
}, },
"python/iqpilot_private/navd/web_server.cpython-312-aarch64-linux-gnu.so": { "python/iqpilot_private/navd/web_server.cpython-312-aarch64-linux-gnu.so": {
"mode": 493, "mode": 493,
"sha256": "2f49a31d16f1952eda641c9634f7450343d2f8289b5e050a44a629778965e23c", "sha256": "ea7ad6c47b9597cb620a21a9328c4086acf1e688df3f7e9215e0b80204c7c36c",
"size": 201400 "size": 201400
}, },
"runtime": { "runtime": {
@@ -331,26 +336,27 @@
} }
}, },
"signatures": { "signatures": {
"python/iqpilot_private/navd/__init__.cpython-312-aarch64-linux-gnu.so": "F5zYUvVEwqzW7SDJKbt0AABXyKtgJ7gO5M/akVREPJC5xgD2PuKSlxhdSnNmb8Q9LyaTbR8x3PSvSXngub+nDQ==", "python/iqpilot_private/navd/__init__.cpython-312-aarch64-linux-gnu.so": "oPML6Wc2Gz1fcV9/pvBqum19QGaDdU6dgx4XxyW+riBWG0SCYhQN49tYqIlCYG0sFhmNTxk+t7PooiDR6IFGDA==",
"python/iqpilot_private/navd/amap_client.cpython-312-aarch64-linux-gnu.so": "/DOq7cGdZbH9s+pn5ey4IXO/eO1ubKg093e91ME9NVXy/F3y8+RjwZMwfqEQQSVC4pe8623LSRV4Z/pCUJFyDA==", "python/iqpilot_private/navd/alpr_matcher.cpython-312-aarch64-linux-gnu.so": "ny5TsakqUUusA8CHaRAHr2u8I36TJMEUYUlIj6QVyLL2+lSdx4EILBp4CPxlCbKD0GXHmtz7e3cUXjQ8ZeEwAg==",
"python/iqpilot_private/navd/event_builder.cpython-312-aarch64-linux-gnu.so": "09miAJIhesOTdcFV2lZBKXSca/q+kKVD6U9wkEShaOESznVs1uZhILBFu4PiKBsALiQBZ/C2s/uQuC1IIItaDw==", "python/iqpilot_private/navd/amap_client.cpython-312-aarch64-linux-gnu.so": "NphgdvZnPhZGkKWHxb9MGuZBfNMIiZPAFZ52c554F0894KWrInZNVgejQO7j+uz2QnS3ZQdR/PlFmI1kdDi+Aw==",
"python/iqpilot_private/navd/helpers.cpython-312-aarch64-linux-gnu.so": "1yKbUBIkSRX4kH+r268peztYOIKVVlAeKhiNShf1Kjc6/ROB/B9vSgE+NLQrNhsMyGkpOEk8HA97JJ8QxndvDA==", "python/iqpilot_private/navd/event_builder.cpython-312-aarch64-linux-gnu.so": "adGUan4KdFX6rUvqTlURR6ZRPiW5H3ssuIR66TRmNbTUetkU6dmNNl4YhEslaKSz1cYQHyEpHwdU2P+DjwKDCg==",
"python/iqpilot_private/navd/incident_feed.cpython-312-aarch64-linux-gnu.so": "NvjR3FoKI567nWq3++SzPQkmCDCaDSTDPfE0GjEHgSBd/SJariIANKwSJCoPhZ436NbsrAxPY9ZJF3zZTQkFBg==", "python/iqpilot_private/navd/helpers.cpython-312-aarch64-linux-gnu.so": "NXYguIzqCJ0R2KFlweClB4KVYpLmWhVo125EtvYFqKYEQrPQFc9gPv5z9ximd4uJkeTDSJbDBEtgESscs2tyAA==",
"python/iqpilot_private/navd/iqmapd.cpython-312-aarch64-linux-gnu.so": "2omO3UL4OfaZ0esBEl1a2x/j+yic0OPNFcolUA+8y+s8/h9IZMe2bUcCbrOai3l4Rpm9BKunwl4EPtj3KiItDg==", "python/iqpilot_private/navd/incident_feed.cpython-312-aarch64-linux-gnu.so": "ng6Y0DD3j+U/Y6LLH5yK5Z5di/FtAnze+nU6EOlE/5v8a0ITw0TDKe90OeiUUWV+vH580sec0DwzNuFyxKodAA==",
"python/iqpilot_private/navd/long_decel.cpython-312-aarch64-linux-gnu.so": "bchPSpi1tnIEe4Kidfo3iadRIHx2EymC5cUT6OBWpLuuk+ND23IsRw1kvLXGpp99hMzXtOwcfmEL79kEjqe6CQ==", "python/iqpilot_private/navd/iqmapd.cpython-312-aarch64-linux-gnu.so": "HihBFDE4shygX42LHhQK3+FBqEtwDhpdkgavEzIi4o9G1IoDHJw3x1waUYq4f53pVORmHdigRlhCHAzCu0QBCA==",
"python/iqpilot_private/navd/mapbox_client.cpython-312-aarch64-linux-gnu.so": "lejZRjrSDLGyVhTYq78QbozsgQxldbqq8z8MkNTB4ZFM7DXup5vUlOu7YJbdrEMs6JWVQsmKUJ67Eq6Fi5UhCA==", "python/iqpilot_private/navd/long_decel.cpython-312-aarch64-linux-gnu.so": "XTu5FBSn9wzzUbyFpYqtEI6T8xFyIaY4JheqW2laPKx+opAhY8OGyniRdc8WQm28/YkYh7alFHcbmkE0ZRMOAw==",
"python/iqpilot_private/navd/nav_cameras.cpython-312-aarch64-linux-gnu.so": "F+YDu+AaFvNnSgAmdRfK3/QUGTHPmARLHeWF/RFPqE8aKOFgqJu5dIKKvuW+8UDc2HG+SQ7xaBe/rqnIQrRHAg==", "python/iqpilot_private/navd/mapbox_client.cpython-312-aarch64-linux-gnu.so": "KOXmWcb8Pv+Y8h2hbAVu86CfKvwM2STRljCkiaLFayPa+s7ItISkwwLeaj94EQpg7QW0MGKGXBznZJRHBtM9DA==",
"python/iqpilot_private/navd/nav_turn_signals.cpython-312-aarch64-linux-gnu.so": "hKjPTpXjUUVobswo8TNWWmLcVvNYd7ADwj5N9APHnoXpkEbKHmaODtGNo5JjpNSoYxVDBnjm0FiYD7gXAtXmBQ==", "python/iqpilot_private/navd/nav_cameras.cpython-312-aarch64-linux-gnu.so": "2XWDcUR+oJH0invIRcOfERVwwUhC8ATks+gYWHMw8FlTKjSqbLhzY3TyWB2c1MIpF311c968R0wc4ZmCmmdSDw==",
"python/iqpilot_private/navd/navd.cpython-312-aarch64-linux-gnu.so": "Jbfvl7PfzijCl/bWoAf0IoHrHvgS2xAgMpXfCBfI1tLzPrXTc6LadxKBPlVJfoNH2WBt1LqnIsjBlLcU0ysyCA==", "python/iqpilot_private/navd/nav_turn_signals.cpython-312-aarch64-linux-gnu.so": "uR1wgK2h8ouVr+Po1brESGiDhbpxBEBqFGq9Ek+woiaceJO9njiTKSOBSldp3653sGIPuZ4++7yRMmzCd7GDCw==",
"python/iqpilot_private/navd/navincidentd.cpython-312-aarch64-linux-gnu.so": "5ybfD0zGZHLat8RUcQGgJmF2z4yPFEBUmMTp5+BdYP4IYa9m5rbUxAXLQSHUAFfM4X3pHSl4pVkBk8Uz30g3AA==", "python/iqpilot_private/navd/navd.cpython-312-aarch64-linux-gnu.so": "zMtyBW+eW3Phtljgoq8dEHThhyeenDq9IPkRCPiWzdZoSysKWmOz0cDnhuLihgaJJDcOz4msp5aggqzHsKlRCQ==",
"python/iqpilot_private/navd/navrenderd.cpython-312-aarch64-linux-gnu.so": "oabGQM48/eoQDLo1/csw/nzGCBAaoUmalk1I6lO0yPmh16wvweGrUZ81IB9oIjmeQYmUcdsKbSi1HMlvV6f2Cw==", "python/iqpilot_private/navd/navincidentd.cpython-312-aarch64-linux-gnu.so": "J3ZnYTzWLxwDUrmwh+d2rKmtMM/t6eGfSYQQoihLltX5LfXZHRUjnk/CMbr619iJWw2srmX/HT2LWyjh+h7vBw==",
"python/iqpilot_private/navd/osm_cameras.cpython-312-aarch64-linux-gnu.so": "DwBKUAvtohLfZSwMDTYfEU8ghoG5LGKQYOB8j/8y77dCSng5kjPL9kvnOXwO4F/SfK/VqjOpMbQyK6V4EVvtCw==", "python/iqpilot_private/navd/navrenderd.cpython-312-aarch64-linux-gnu.so": "E340TW5CssaeUeA/xioykmrvsVJUZDVe5mUpUrdCUyG6qVta/V+6ZxvM+n9FEjUuqHO5w0U1Gog7SMU6QguEDA==",
"python/iqpilot_private/navd/reroute.cpython-312-aarch64-linux-gnu.so": "CULSqD7g2vYgxtB+FLZMVGUY/tvtLXbd/jzmA7xt1W2Wla6cV4OS+xKXz6tpLA0o0MQFVUwFHEp4xMMbZ/kqAA==", "python/iqpilot_private/navd/osm_cameras.cpython-312-aarch64-linux-gnu.so": "l3iGDUFF+LGssB6mrgoOoB/P4M4FeoDQmmZgYzjoQArM0DjXN51yHC1oAuVUikMC1QqI8JEz+pT/t3K/lthDDA==",
"python/iqpilot_private/navd/road_incidents.cpython-312-aarch64-linux-gnu.so": "X/FYviFDp4KlDv3qhVpvXVIDcC8dL0NcxyTKXN4DRy1/9PGIAvydNIl4ViLR6JcP7hFd+KeEXT4WjaXrxJUHCw==", "python/iqpilot_private/navd/reroute.cpython-312-aarch64-linux-gnu.so": "dR976C5dqTNV2z2I9RXcfxT/SSwg+IRaIcXrJM6/uL2rjyTW4E0avjJwgIH7GAYyOKfdRdWaBhoeIpfpDu18Aw==",
"python/iqpilot_private/navd/route_manager.cpython-312-aarch64-linux-gnu.so": "QWPpQTkK/589D84nSAqtauyiLwHZRlcRwFSmubLyRr8xv2UKh+vfHSHT+s3EUYcl73k3tJrgQlNZAqNU+RVEAA==", "python/iqpilot_private/navd/road_incidents.cpython-312-aarch64-linux-gnu.so": "n5h8gG43Se5sGYqwc+OAmu2JF2mWKZ6yHz+Zm62qwYuImz/3KMC8dP4tXWrCsZNeOYhIWhQ8/rtrUA2+99GDBg==",
"python/iqpilot_private/navd/runtime_common.cpython-312-aarch64-linux-gnu.so": "bE6I2JK6hfoWkhpJ6m8zgK6ZXDWignEQE+titwgQkYU+BXv1W7ZjxW5anHwTN5N/nXu3nDzRPxQmATRB4QYEDw==", "python/iqpilot_private/navd/route_manager.cpython-312-aarch64-linux-gnu.so": "pnE75vxRBGvNdNGna3EETR5/XIVm6++qFo+RiwII/Cfr/qLy2htTdGRv7sVz419HR65NbrK1XNOhnomgaeKvCg==",
"python/iqpilot_private/navd/traffic.cpython-312-aarch64-linux-gnu.so": "F+oel9VPRD18u70lYd7h3Qw7Ispwm8ttsPORlUvDZUqcxPvtL5vYt8gZ7joTRA3hdyrDqLEXvLOEUyEljJwGAA==", "python/iqpilot_private/navd/runtime_common.cpython-312-aarch64-linux-gnu.so": "wbXLXRjX9hsV1fhYcHhqii6+ejytkL8cBWROmUjc4S0zJCMj5+h+egXl8Pdg7R2X0ylFM6Ifxa3xvyT6CwMuDA==",
"python/iqpilot_private/navd/turn_desire.cpython-312-aarch64-linux-gnu.so": "ytdGaSS1/ME/0LZFzmQ+DMjWsToxQAWRqVMCv+gHYOYwM8ye3xjJwR2mi5rVfI2CvyWM4XT2AXRNfTotEDEVAA==", "python/iqpilot_private/navd/traffic.cpython-312-aarch64-linux-gnu.so": "DztyZU20SV8yBmCxA8RVRQQgUyOsKYPpvFaXlOOI7qrlWEJUFedw1CPhh+OEfPtqETHtAD/RbYtNTJvJxdOEBA==",
"python/iqpilot_private/navd/web_server.cpython-312-aarch64-linux-gnu.so": "Wll0YO5ZFHblaCKwip3269WhENgac4VII8DUdA0G51V3Ru/SxxXetW6LSnJuCERYYnhwqEM5SxizXrVJSIYmCA==" "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=="
} }
} }

View File

@@ -1 +1 @@
qir1it9kZm0rKWklTiwgR7rtCnv5YABlSkR4Mg+0lGlsBrw7mWRFJ81zSR+uOTgJllPAugTSzlAck8lHpXZCAQ== WJGvk92Vi3ftfwgoTBFWBFrdBiRyw1rJNRMjFcAsqEAcbBlA3tZWbUNmqLqiJamhrGc7pWX/1j2jEB0dImvZBg==

View File

@@ -6,6 +6,7 @@ from iqpilot.common.constants import CV
from iqpilot.cereal import car, custom from iqpilot.cereal import car, custom
from iqdbc.car import structs from iqdbc.car import structs
from iqpilot.common.params import Params 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 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.v_cruise_kph_last = 0
self.button_timers = {ButtonType.decelCruise: 0, ButtonType.accelCruise: 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.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 @property
def v_cruise_initialized(self): def v_cruise_initialized(self):
@@ -248,6 +278,7 @@ class VCruiseHelper(VCruiseHelperIQ):
def update_v_cruise(self, CS, enabled, is_metric): def update_v_cruise(self, CS, enabled, is_metric):
self.v_cruise_kph_last = self.v_cruise_kph self.v_cruise_kph_last = self.v_cruise_kph
self._update_slc_accel_gesture(CS)
self.get_minimum_set_speed(is_metric) 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 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 # 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._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.update_speed_limit_assist_v_cruise_non_pcm()
self.v_cruise_cluster_kph = self.v_cruise_kph self.v_cruise_cluster_kph = self.v_cruise_kph
self.update_button_timers(CS, enabled) self.update_button_timers(CS, enabled)
else: else:
self.v_cruise_kph = CS.cruiseState.speed * CV.MS_TO_KPH self.v_cruise_kph = CS.cruiseState.speed * CV.MS_TO_KPH
self.v_cruise_cluster_kph = CS.cruiseState.speedCluster * 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: if CS.cruiseState.speed == 0:
self.v_cruise_kph = V_CRUISE_UNSET self.v_cruise_kph = V_CRUISE_UNSET
self.v_cruise_cluster_kph = V_CRUISE_UNSET self.v_cruise_cluster_kph = V_CRUISE_UNSET
@@ -273,6 +307,7 @@ class VCruiseHelper(VCruiseHelperIQ):
else: else:
self.v_cruise_kph = V_CRUISE_UNSET self.v_cruise_kph = V_CRUISE_UNSET
self.v_cruise_cluster_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): 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 # handle button presses. TODO: this should be in state_control, but a decelCruise press

View File

@@ -474,6 +474,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"SpeedCameraSlowdown", {PERSISTENT, BOOL, "0"}}, {"SpeedCameraSlowdown", {PERSISTENT, BOOL, "0"}},
{"SpeedCameraSafetyFactor", {PERSISTENT, FLOAT, "1.0"}}, {"SpeedCameraSafetyFactor", {PERSISTENT, FLOAT, "1.0"}},
{"NavCamerasData", {PERSISTENT, JSON}}, {"NavCamerasData", {PERSISTENT, JSON}},
{"NavCameraRoadGraphData", {PERSISTENT, JSON}},
{"WazePoliceApiKey", {PERSISTENT | DONT_LOG, STRING}}, {"WazePoliceApiKey", {PERSISTENT | DONT_LOG, STRING}},
{"WazePoliceAlertMode", {PERSISTENT, INT, "0"}}, {"WazePoliceAlertMode", {PERSISTENT, INT, "0"}},
{"WazePoliceShadow", {PERSISTENT, BOOL, "0"}}, {"WazePoliceShadow", {PERSISTENT, BOOL, "0"}},

View File

@@ -6,12 +6,16 @@ from __future__ import annotations
import hashlib import hashlib
import json import json
import os import os
from iqpilot.common.swaglog import cloudlog
import urllib.request import urllib.request
from pathlib import Path from pathlib import Path
from iqpilot.system.hardware.usb import egpu_dock_ready from iqpilot.system.hardware.usb import egpu_dock_ready
USB_SYSFS_ROOT = "/sys/bus/usb/devices" 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" 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") 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: def onnx_cache_path(meta: dict) -> str:
from iqpilot.system.hardware.hw import Paths from iqpilot.system.hardware.hw import Paths
return os.path.join(Paths.model_root(), f"{meta['model_name']}_{meta['sha256'][:8]}.onnx") 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: if not download_url:
raise RuntimeError(f"model {meta['key']} has no download source; stage the onnx at {onnx_cache_path(meta)}") 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) path = onnx_cache_path(meta)
os.makedirs(os.path.dirname(path), exist_ok=True) 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" tmp = path + ".part"
digest = hashlib.sha256() digest = hashlib.sha256()
got = 0 got = 0
@@ -142,12 +156,20 @@ def download_onnx(meta: dict, progress_cb=None) -> str:
return path return path
def download_precompiled(meta: dict, progress_cb=None, policy: bool = False) -> str | None: def download_precompiled(meta: dict, progress_cb=None, policy: bool = False, oob: bool = False) -> str | None:
art = meta.get("egpu_policy_artifact" if policy else "egpu_artifact") field = "egpu_oob_artifact" if oob else "egpu_policy_artifact" if policy else "egpu_artifact"
if not art or not art.get("objects"): art = meta.get(field)
if not art or not (art.get("objects") or art.get("hf_path")):
return None return None
from iqpilot.selfdrive.iqmodeld.model_bundle_downloader import download_lfs_bundle from iqpilot.selfdrive.iqmodeld.model_bundle_downloader import download_hf_file, download_lfs_bundle
dest = egpu_policy_pkl_path(meta) if policy else egpu_pkl_path(meta) 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) 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 _orig = helpers.fetch_fw
def fetch_fw(path, name, sha256): 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") p = pathlib.Path(f"/lib/firmware/{path}/{name}.zst")
if p.is_file(): if p.is_file():
blob = zstandard.ZstdDecompressor().stream_reader(p.read_bytes()).read() blob = zstandard.ZstdDecompressor().stream_reader(p.read_bytes()).read()
if hashlib.sha256(blob).hexdigest() == sha256: if hashlib.sha256(blob).hexdigest() == sha256:
return blob 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 fetch_fw._iq_patched = True
helpers.fetch_fw = fetch_fw helpers.fetch_fw = fetch_fw

View File

@@ -3,11 +3,18 @@ Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed
""" """
from __future__ import annotations from __future__ import annotations
import io
import math import math
import os
import pickle
import shutil
import struct
import tempfile
import numpy as np import numpy as np
POLICY_FORMAT = 2 POLICY_FORMAT = 2
OOB_MAGIC = b"IQEGPUOOB1"
QUEUE_NAMES = ("img_q", "big_img_q", "feat_q", "desire_q") QUEUE_NAMES = ("img_q", "big_img_q", "feat_q", "desire_q")
PACKED_ORDER = ("desire", "traffic_convention", "action_t", "prev_feat") PACKED_ORDER = ("desire", "traffic_convention", "action_t", "prev_feat")
@@ -115,3 +122,50 @@ class PolicyRunner:
flat = out.numpy().reshape(-1) flat = out.numpy().reshape(-1)
v["prev_feat"][:] = flat[self._hidden].reshape(v["prev_feat"].shape) v["prev_feat"][:] = flat[self._hidden].reshape(v["prev_feat"].shape)
return flat 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("<q", m.nbytes))
tmp.write(m)
pb.release()
stream = io.BytesIO()
pickle.Pickler(stream, protocol=5, buffer_callback=buffer_callback).dump(obj)
opcodes = stream.getvalue()
f.write(struct.pack("<q", len(opcodes)))
f.write(opcodes)
tmp.seek(0)
shutil.copyfileobj(tmp, f)
def is_oob(path: str) -> 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("<q", f.read(8))[0])
def buffers():
while (h := f.read(8)):
pb = pickle.PickleBuffer(bytearray(struct.unpack("<q", h)[0]))
f.readinto(pb)
yield pb
return pickle.load(io.BytesIO(opcodes), buffers=buffers())
def load_bundle(path: str):
with open(path, "rb") as f:
if f.read(len(OOB_MAGIC)) == OOB_MAGIC:
f.seek(0)
return load_oob(f)
f.seek(0)
return pickle.load(f)

View File

@@ -0,0 +1,99 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
from __future__ import annotations
import glob
import os
os.environ.setdefault("XDG_CACHE_HOME", "/data/.cache")
import time
from iqpilot.common.params import Params
from iqpilot.common.swaglog import cloudlog
from iqpilot.selfdrive.iqmodeld.egpu_helpers import download_precompiled, egpu_policy_pkl_path, egpu_selected, patch_tinygrad_fetch_fw, usbgpu_present
from iqpilot.selfdrive.iqmodeld.egpu_model import resolve_egpu_model
POLL_S = 30.0
RETRY_S = 120.0
def _selected_meta(params: Params) -> 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()

View File

@@ -4,6 +4,7 @@ Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed
from __future__ import annotations from __future__ import annotations
import os import os
os.environ.setdefault("XDG_CACHE_HOME", "/data/.cache")
import pickle import pickle
import subprocess import subprocess
import sys 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, DESIRE_LEN, LAT_SMOOTH_SECONDS, LONG_SMOOTH_SECONDS, get_action_from_model,
) )
from iqpilot.selfdrive.iqmodeld.egpu_helpers import ( 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, patch_tinygrad_fetch_fw, quarantine_artifact, resolve_backend, usbgpu_present,
) )
from iqpilot.selfdrive.iqmodeld.egpu_model import resolve_egpu_model 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.messaging import DrivePacketMemory, populate_drive_messages, populate_odometry_message
from iqpilot.selfdrive.iqmodeld.metadata import Meta20hz from iqpilot.selfdrive.iqmodeld.metadata import Meta20hz
from iqpilot.selfdrive.iqmodeld.model_channel import BIG_CHANNEL, ModelChannel 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.model_warp import FrameWarp
from iqpilot.selfdrive.iqmodeld.parser import PhaseParser from iqpilot.selfdrive.iqmodeld.parser import PhaseParser
@@ -56,6 +57,9 @@ PROCESS_NAME = "iqpilot.selfdrive.iqmodeld.iqegpumodeld"
PRESENCE_POLL_S = 5.0 PRESENCE_POLL_S = 5.0
COMPILE_TIMEOUT_S = 3600 COMPILE_TIMEOUT_S = 3600
LINK_UP_TIMEOUT_S = 10.0 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_BASE_S = 3.0
SETUP_RETRY_MAX_S = 30.0 SETUP_RETRY_MAX_S = 30.0
@@ -106,14 +110,38 @@ _precompiled_tried = False
def _ensure_artifact(params: Params, meta: dict) -> str: def _ensure_artifact(params: Params, meta: dict) -> str:
global _precompiled_tried 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) policy_path = egpu_policy_pkl_path(meta)
if os.path.isfile(policy_path):
return policy_path
legacy_path = egpu_pkl_path(meta) legacy_path = egpu_pkl_path(meta)
params.put_bool("UsbGpuCompiled", False) params.put_bool("UsbGpuCompiled", False)
params.put_bool("UsbGpuReady", 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: if meta.get("egpu_policy_artifact") and not _precompiled_tried:
_precompiled_tried = True _precompiled_tried = True
params.put("UsbGpuSetupProgress", "0.0") params.put("UsbGpuSetupProgress", "0.0")
@@ -157,12 +185,34 @@ def _ensure_artifact(params: Params, meta: dict) -> str:
return policy_path 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): def _load_infer_fn(pkl_path: str, meta: dict):
patch_tinygrad_fetch_fw() patch_tinygrad_fetch_fw()
from tinygrad.tensor import Tensor from tinygrad.tensor import Tensor
with open(pkl_path, "rb") as f: _wait_for_memory(MIN_LOAD_AVAIL_MB)
bundle = pickle.load(f) bundle = load_bundle(pkl_path)
if bundle.get("model_sha256") != meta["sha256"]: if bundle.get("model_sha256") != meta["sha256"]:
quarantine_artifact(pkl_path, "pkl model sha mismatch") quarantine_artifact(pkl_path, "pkl model sha mismatch")
raise RuntimeError(f"artifact model sha {bundle.get('model_sha256')} != {meta['sha256']}") raise RuntimeError(f"artifact model sha {bundle.get('model_sha256')} != {meta['sha256']}")
@@ -241,8 +291,14 @@ def main(demo: bool = False) -> None:
break break
except Exception as e: except Exception as e:
attempt += 1 attempt += 1
params.put("UsbGpuLastError", str(e)[:512]) subs = "; ".join(f"{type(x).__name__}: {x}" for x in (getattr(e, "exceptions", None) or []))
cloudlog.warning(f"iqegpumodeld setup attempt {attempt} failed: {e}; retrying") 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(): if not usbgpu_present():
_wait_for_egpu(params) _wait_for_egpu(params)
time.sleep(min(SETUP_RETRY_MAX_S, SETUP_RETRY_BASE_S * attempt)) time.sleep(min(SETUP_RETRY_MAX_S, SETUP_RETRY_BASE_S * attempt))

View File

@@ -27,6 +27,63 @@ def _requests_auth():
return None 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: def _lfs_endpoint(base_url: str) -> str:
return base_url.split("/raw/", 1)[0] + ".git/info/lfs" 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", {}) 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: def download_lfs_bundle(objects: list, dst: str, sha256: str, size: int, progress_cb=None) -> str:
import requests import requests
auth = _requests_auth() auth = _requests_auth()
session = requests.Session() session = requests.Session()
os.makedirs(os.path.dirname(dst), exist_ok=True) os.makedirs(dst + ".parts", exist_ok=True)
tmp = dst + ".part"
total = int(size) or sum(int(o["size"]) for o in objects) 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 last_error: Exception | None = None
for base_url in MODELS_BASE_URLS: for base_url in MODELS_BASE_URLS:
for attempt in range(STREAM_RETRIES): for _attempt in range(STREAM_RETRIES):
try: try:
digest = hashlib.sha256()
got = 0
with open(tmp, "wb") as f:
for obj in objects: for obj in objects:
href, headers = _resolve_oid(session, base_url, obj["oid"], int(obj["size"]), auth) path = _part_path(dst, obj["oid"])
obj_auth = None if headers.get("Authorization") else auth if _part_complete(path, obj["oid"], int(obj["size"])):
with session.get(href, headers=headers, stream=True, timeout=120, auth=obj_auth) as r: continue
r.raise_for_status() got[0] = done_bytes
for chunk in r.iter_content(CHUNK): _fetch_part(session, base_url, obj, path, auth, progress)
f.write(chunk) if not _part_complete(path, obj["oid"], int(obj["size"])):
digest.update(chunk) if os.path.getsize(path) >= int(obj["size"]):
got += len(chunk) os.remove(path)
if progress_cb is not None and total: raise RuntimeError(f"part {obj['oid'][:12]} incomplete or failed verification")
progress_cb(min(1.0, got / total)) done_bytes += int(obj["size"])
if total and got != total: got[0] = done_bytes
raise RuntimeError(f"size mismatch: {got}/{total} bytes") break
if sha256 and digest.hexdigest() != sha256:
raise RuntimeError("sha256 mismatch")
os.replace(tmp, dst)
return dst
except Exception as e: except Exception as e:
last_error = e last_error = e
try: 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) 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: except OSError:
pass pass
raise RuntimeError(f"model bundle download failed: {last_error}") 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

View File

@@ -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

View File

@@ -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

View File

@@ -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"}

View File

@@ -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",)]

View File

@@ -7,14 +7,20 @@ import argparse
import gc import gc
import os import os
import pickle import pickle
import sys
import time import time
os.environ.setdefault("DEV", "USB+AMD:LLVM")
os.environ.setdefault("FLOAT16", "1") os.environ.setdefault("FLOAT16", "1")
os.environ.setdefault("JIT_BATCH_SIZE", "0") os.environ.setdefault("JIT_BATCH_SIZE", "0")
os.environ.setdefault("GMMU", "0") os.environ.setdefault("GMMU", "0")
os.environ.setdefault("TC_OPT", "2") 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 import numpy as np
from iqpilot.selfdrive.iqmodeld.egpu_helpers import egpu_pkl_path, local_onnx, patch_tinygrad_fetch_fw 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.engine.jit import TinyJit
from tinygrad.nn.onnx import OnnxRunner 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"): if meta.get("split"):
raise RuntimeError(f"model {meta['key']} is a split model; eGPU compiles fused models only") 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) baseline = step(SEED + i)
if baseline.shape[0] != meta["output_len"]: if baseline.shape[0] != meta["output_len"]:
raise RuntimeError(f"model output length {baseline.shape[0]} != registry {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") raise RuntimeError("compiled policy produced non-finite outputs")
bundle = { 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) os.makedirs(os.path.dirname(out_path), exist_ok=True)
tmp = out_path + ".part" tmp = out_path + ".part"
print("serialize") print("serialize (out-of-band buffers)")
with open(tmp, "wb") as f: with open(tmp, "wb") as f:
pickle.dump(bundle, f, protocol=pickle.HIGHEST_PROTOCOL) dump_oob(bundle, f)
del bundle, jit, queues, packed del bundle, jit, queues, packed
gc.collect() gc.collect()
print("reload + validate") print("reload + validate")
with open(tmp, "rb") as f: jit = load_bundle(tmp)["run_policy"]
jit = pickle.load(f)["run_policy"]
queues = make_queues(input_spec, frame_skip, device) queues = make_queues(input_spec, frame_skip, device)
packed = PackedInputs(input_spec) packed = PackedInputs(input_spec)
outs = [] outs = []
@@ -211,6 +216,9 @@ def compile_policy_model(meta: dict, onnx_path: str, out_path: str) -> str:
flat = out.numpy().reshape(-1) flat = out.numpy().reshape(-1)
packed.views["prev_feat"][:] = flat[meta["output_slices"]["hidden_state"]].reshape(packed.views["prev_feat"].shape) packed.views["prev_feat"][:] = flat[meta["output_slices"]["hidden_state"]].reshape(packed.views["prev_feat"].shape)
outs.append(flat) outs.append(flat)
if HOST:
os.replace(tmp, out_path)
return out_path
if not np.array_equal(outs[-1], baseline): if not np.array_equal(outs[-1], baseline):
raise RuntimeError("policy outputs differ from baseline after pickle round trip") raise RuntimeError("policy outputs differ from baseline after pickle round trip")
if np.array_equal(outs[0], outs[-1]): 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-base", type=float, default=None)
p.add_argument("--progress-span", type=float, default=0.0) p.add_argument("--progress-span", type=float, default=0.0)
p.add_argument("--format", type=int, default=2, choices=(1, 2)) 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() 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 is not None:
if args.model in EGPU_MODELS: if args.model in EGPU_MODELS:

View File

@@ -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()

View File

@@ -0,0 +1,60 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
from __future__ import annotations
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

View File

@@ -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("<q", len(arg)))
bufs.write(arg)
moved += 1
else:
ops_stream += data[pos:end]
out.write(OOB_MAGIC)
out.write(struct.pack("<q", len(ops_stream)))
out.write(ops_stream)
with open(tmp, "ab") as out, open(tmp + ".buf", "rb") as bufs:
while chunk := bufs.read(1 << 24):
out.write(chunk)
os.remove(tmp + ".buf")
os.replace(tmp, dst)
return moved, len(ops)
def main() -> 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()

View File

@@ -375,7 +375,7 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
"Pay Attention", "Pay Attention",
"", "",
AlertStatus.normal, AlertSize.small, AlertStatus.normal, AlertSize.small,
Priority.LOW, VisualAlert.none, AudibleAlert.none, .1), Priority.MID, VisualAlert.none, AudibleAlert.none, .1),
}, },
EventName.promptDriverDistracted: { EventName.promptDriverDistracted: {
@@ -901,7 +901,7 @@ if HARDWARE.get_device_type() == 'mici':
"Pay Attention", "Pay Attention",
"", "",
AlertStatus.normal, AlertSize.small, AlertStatus.normal, AlertSize.small,
Priority.LOW, VisualAlert.none, AudibleAlert.none, 2), Priority.MID, VisualAlert.none, AudibleAlert.none, 2),
}, },
EventName.promptDriverDistracted: { EventName.promptDriverDistracted: {
ET.PERMANENT: Alert( ET.PERMANENT: Alert(

View File

@@ -1,5 +1,6 @@
import iqpilot.cereal.messaging as messaging import iqpilot.cereal.messaging as messaging
from iqpilot.cereal import log, car, custom from iqpilot.cereal import log, car, custom
from iqpilot.common.params import Params
from iqpilot.common.constants import CV from iqpilot.common.constants import CV
from iqpilot.common.atlas_alerts import EventBook as EventsBase, Tier as Priority, Tags as ET, AlertCard as Alert, \ 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, \ NoEntryCard as NoEntryAlert, HardDisableCard as ImmediateDisableAlert, ChimeCard as EngagementAlert, \
@@ -94,6 +95,24 @@ _CAMERA_LABELS = {
} }
_POLICE_CHIMED_IDS: set[str] = set() _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: 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)) ctype = int(getattr(nav.cameraType, "raw", nav.cameraType))
label = _CAMERA_LABELS.get(ctype, "Speed Camera") label = _CAMERA_LABELS.get(ctype, "Speed Camera")
distance = float(nav.cameraDistance) 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 # 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. # distance — flockd/navd flag it with distance 0 on the alpr camera type.
if ctype == int(custom.IQNavState.CameraType.alpr) and distance <= 0.0: if ctype == int(custom.IQNavState.CameraType.alpr) and distance <= 0.0:
return Alert( return Alert(
"Flock Camera Detected", alpr_detected_label,
"", "",
AlertStatus.normal, AlertSize.small, AlertStatus.normal, AlertSize.small,
Priority.HIGH, VisualAlert.none, AudibleAlert.prompt, .2) Priority.LOW, VisualAlert.none, AudibleAlert.prompt, .2)
if metric: if metric:
dist_str = f"{distance:.0f} m" if distance < 1000.0 else f"{distance / 1000.0:.1f} km" dist_str = f"{distance:.0f} m" if distance < 1000.0 else f"{distance / 1000.0:.1f} km"
else: else:
@@ -134,7 +156,8 @@ def speed_camera_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMas
f"{label}{detail}", f"{label}{detail}",
"", "",
AlertStatus.normal, AlertSize.small, 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): class IQEvents(EventsBase):

View File

@@ -1,13 +1,17 @@
import copy
from types import SimpleNamespace 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 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( nav = SimpleNamespace(
cameraType=camera_type, cameraType=camera_type,
cameraDistance=300.0, cameraDistance=distance,
cameraSpeedLimit=25.0, cameraSpeedLimit=25.0,
cameraAlertId=report_id, cameraAlertId=report_id,
cameraChime=chime, 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) second = alert(custom.IQNavState.CameraType.police, report_id="police-a", chime=True)
assert first.audible_alert == car.CarControl.HUDControl.AudibleAlert.prompt assert first.audible_alert == car.CarControl.HUDControl.AudibleAlert.prompt
assert second.audible_alert == car.CarControl.HUDControl.AudibleAlert.none 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

View File

@@ -222,6 +222,8 @@ procs += [
and_(only_onroad, and_(is_tinygrad_model, emac_enabled)), restart_if_crash=True), and_(only_onroad, and_(is_tinygrad_model, emac_enabled)), restart_if_crash=True),
PythonProcess("iqegpumodeld", "iqpilot.selfdrive.iqmodeld.iqegpumodeld", PythonProcess("iqegpumodeld", "iqpilot.selfdrive.iqmodeld.iqegpumodeld",
and_(only_onroad, and_(is_tinygrad_model, egpu_enabled)), restart_if_crash=True), 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", BundleProcess("backup_manager_k3", "iqpilot_hephaestusd_private", "iqpilot_private.konn3kt.backups.backup_orchestrator",
and_(only_offroad, hephaestus_ready_shim, not_low_power)), and_(only_offroad, hephaestus_ready_shim, not_low_power)),