IQ.Pilot Release Commit @ 763bad7

This commit is contained in:
IQ.Lvbs CI [bot]
2026-08-29 19:58:49 -05:00
parent ee1dca77c7
commit 99807e037f
41 changed files with 876 additions and 57 deletions

View File

@@ -1,8 +1,8 @@
{ {
"python/_iqclosure/_vendored.json": { "python/_iqclosure/_vendored.json": {
"mode": 420, "mode": 420,
"sha256": "c5dde802e17116d9759e661e7d80a1a86d396fb3040734c1eadb2a50c5a3cf4c", "sha256": "2cd59a784bfe6c6d1ce382e832dd355f62900b1f73a74ed91c53397aa8581754",
"size": 3439 "size": 3495
}, },
"python/_iqclosure/iqpilot/__init__.py": { "python/_iqclosure/iqpilot/__init__.py": {
"mode": 420, "mode": 420,
@@ -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": "5d00df0cf831ea75a91d1d817818dc3847d971e581170671f12e289461eccabc", "sha256": "4df0ac0a3f720ee01a70f82908e582682e84848023592a72bd45f2ca74c9b161",
"size": 4846 "size": 5535
}, },
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/egpu_model.py": { "python/_iqclosure/iqpilot/selfdrive/iqmodeld/egpu_model.py": {
"mode": 420, "mode": 420,
@@ -219,6 +219,11 @@
"sha256": "dd1559099407687ee2dc81e180e05b9954e712c39e1a82e39c1b00b70c43374a", "sha256": "dd1559099407687ee2dc81e180e05b9954e712c39e1a82e39c1b00b70c43374a",
"size": 3016 "size": 3016
}, },
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/model_bundle_downloader.py": {
"mode": 420,
"sha256": "62c2c2830c7c8819bf42eb54a34db635128a31fbc2bbb71bc95415e5b873a3c0",
"size": 3189
},
"python/_iqclosure/iqpilot/selfdrive/iqmodeld/model_channel.py": { "python/_iqclosure/iqpilot/selfdrive/iqmodeld/model_channel.py": {
"mode": 420, "mode": 420,
"sha256": "2d706993df418501d93dc766ffe452131e565bfbc2e8412b527dc14915e92832", "sha256": "2d706993df418501d93dc766ffe452131e565bfbc2e8412b527dc14915e92832",
@@ -526,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": "0b580306ea908ad46b280f2fd5f828083e736b83c951a33ab007a6fb8a642442", "sha256": "3f55e4c621ea86c0a6ac9ecac091fa90491048df5fc5bf09cbb668f0c0b71051",
"size": 202152 "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": "b4d721d043cad44826a7b1ac304105ff65676a00570e1b1e0f845c2a4ab88d51", "sha256": "e748eba2eec567e5f0c9e1dce60ef71795d2a99f58238ad89495dcee7672c70c",
"size": 267376 "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": "c63d56f9a4329f8a16c4a2144ddc7761876fd20f7c51ac40005c516573f7b19e", "sha256": "46f79e0fe9d799003b49e5fe11969a1716974a9c6d20d52488d79ef8b7137a5a",
"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": "311a13e5db0e6f8719e2b6383a2d9c74c2f70db28bdd424c5ceec4958d70f201", "sha256": "b7acc1edcf50dd2e9c2d88f2f4824d0a28b0def3a57b144a340ddca1bdd14254",
"size": 333336 "size": 333336
}, },
"runtime": { "runtime": {
@@ -553,9 +558,9 @@
} }
}, },
"signatures": { "signatures": {
"python/iqpilot_private/emac/bulk_transport.cpython-312-aarch64-linux-gnu.so": "mxq4MaNi+2F0fpbVaypztLeUf9DqFo8LSohXAzb+1tXsdKZZYQmmU90hlqIRFv4PuwWRyVbXJE/Ge48amXDhBw==", "python/iqpilot_private/emac/bulk_transport.cpython-312-aarch64-linux-gnu.so": "zdchbAhGhL0eeX6qgQUw0CwrPGIg896Sv/0AG0hHrJ/1Ek7KnB4llxmWUA/e4HB4hlCqTEMO416N3csIfDikBA==",
"python/iqpilot_private/emac/mac_client.cpython-312-aarch64-linux-gnu.so": "Zm41VmfRMMJkj3TvxrqLLyUy+0Xq71z9V18gpKkstM5hWtrZsdP4Q+G04eeiujl45Q8hO1ysW0MMP6gJFQGfAg==", "python/iqpilot_private/emac/mac_client.cpython-312-aarch64-linux-gnu.so": "p0ukaERkDTTF6vVVn0TK1ImrPMArUWmqAY2kJnctlb38486SIeFZxC4gCP0cU9++j7i7ydmXNOcUqxG7qIPECQ==",
"python/iqpilot_private/emac/mac_protocol.cpython-312-aarch64-linux-gnu.so": "3quWJnVdRjcA+zrHojJIRcmxySkJAbskBYfVpXjpN3BHxFvjrC+TK9ReVEeTwQdp6b6xR0Fk+40Ngo0MphsbAQ==", "python/iqpilot_private/emac/mac_protocol.cpython-312-aarch64-linux-gnu.so": "5rLpvBoiOP+/N/zblRnXcltpOtULO3uZx2bQYeCM/A8x8m9HLhIBUMJzSVIYj5VcbvgHmzc911UE18RrwEmyCg==",
"python/iqpilot_private/emac/maciqmodeld.cpython-312-aarch64-linux-gnu.so": "wqTQKJ8HOYoBP6dLbdVLvMRZ81kMfvwRB77qSj3jikgyPtz2ARen/Eo/SydGqQByqn0FUR7mHWm65I7/flEEBw==" "python/iqpilot_private/emac/maciqmodeld.cpython-312-aarch64-linux-gnu.so": "otKpewDX8CPqirHPXn+mTht7oi/AgkQiZ2VXPZM3/wryLSvzRKJzMyf5nXZ/lb0Z8J8vsFKUkdVhxKgYkeLIDw=="
} }
} }

View File

@@ -1 +1 @@
ML9DoBJeI+4hKgeyK5ssDKYFVeaAg1dVrqTrEjizxFHJtuAv1wYohJz8hxdJnvkQgvrA4pFVsOsu3kvEdIXWAg== 9uZxIFB8JGkbVFGkwQJ+7gxvqL+EJf7PS0DqSsFYuiaw39lYS4qp5rzPOmn0ixSDSYlzsEwnwq5ojMdbX/ZJCQ==

View File

@@ -34,6 +34,7 @@
"iqpilot.selfdrive.iqmodeld.emac_model_meta", "iqpilot.selfdrive.iqmodeld.emac_model_meta",
"iqpilot.selfdrive.iqmodeld.messaging", "iqpilot.selfdrive.iqmodeld.messaging",
"iqpilot.selfdrive.iqmodeld.metadata", "iqpilot.selfdrive.iqmodeld.metadata",
"iqpilot.selfdrive.iqmodeld.model_bundle_downloader",
"iqpilot.selfdrive.iqmodeld.model_channel", "iqpilot.selfdrive.iqmodeld.model_channel",
"iqpilot.selfdrive.iqmodeld.model_warp", "iqpilot.selfdrive.iqmodeld.model_warp",
"iqpilot.selfdrive.iqmodeld.models", "iqpilot.selfdrive.iqmodeld.models",

View File

@@ -57,6 +57,11 @@ def egpu_pkl_path(meta: dict) -> str:
return os.path.join(Paths.model_root(), f"egpu_{meta['key']}_{meta['sha256'][:8]}_amd_tinygrad.pkl") return os.path.join(Paths.model_root(), f"egpu_{meta['key']}_{meta['sha256'][:8]}_amd_tinygrad.pkl")
def egpu_policy_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.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")
@@ -137,6 +142,15 @@ 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:
art = meta.get("egpu_policy_artifact" if policy else "egpu_artifact")
if not art or not art.get("objects"):
return None
from iqpilot.selfdrive.iqmodeld.model_bundle_downloader import download_lfs_bundle
dest = egpu_policy_pkl_path(meta) if policy else egpu_pkl_path(meta)
return download_lfs_bundle(art["objects"], dest, art["sha256"], int(art.get("size", 0)), progress_cb=progress_cb)
def patch_tinygrad_fetch_fw() -> None: def patch_tinygrad_fetch_fw() -> None:
import pathlib import pathlib

View File

@@ -0,0 +1,87 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
from __future__ import annotations
import hashlib
import json
import os
MODELS_BASE_URLS = (
"https://git.konn3kt.com/teal/IQModels/raw/branch/main",
"https://gitlvb.teallvbs.xyz/teal/IQModels/raw/branch/main",
)
CHUNK = 4 * 1024 * 1024
HTTP_TIMEOUT_S = 60.0
STREAM_RETRIES = 6
def _requests_auth():
import importlib
for mod in ("iqpilot_private.models.git_auth", "iqpilot.models_private_src.git_auth",
"iqpilot.selfdrive.iqmodeld.models.git_auth"):
try:
return importlib.import_module(mod).get_requests_auth()
except Exception:
continue
return None
def _lfs_endpoint(base_url: str) -> str:
return base_url.split("/raw/", 1)[0] + ".git/info/lfs"
def _resolve_oid(session, base_url: str, oid: str, size: int, auth):
import requests
batch = session.post(f"{_lfs_endpoint(base_url)}/objects/batch",
data=json.dumps({"operation": "download", "transfers": ["basic"],
"objects": [{"oid": oid, "size": size}]}),
headers={"Content-Type": "application/vnd.git-lfs+json",
"Accept": "application/vnd.git-lfs+json"},
auth=auth, timeout=HTTP_TIMEOUT_S)
batch.raise_for_status()
entry = batch.json()["objects"][0]
if "actions" not in entry:
raise requests.RequestException(f"LFS object unavailable: {entry.get('error', oid)}")
action = entry["actions"]["download"]
return action["href"], action.get("header", {})
def download_lfs_bundle(objects: list, dst: str, sha256: str, size: int, progress_cb=None) -> str:
import requests
auth = _requests_auth()
session = requests.Session()
os.makedirs(os.path.dirname(dst), exist_ok=True)
tmp = dst + ".part"
total = int(size) or sum(int(o["size"]) for o in objects)
last_error: Exception | None = None
for base_url in MODELS_BASE_URLS:
for attempt in range(STREAM_RETRIES):
try:
digest = hashlib.sha256()
got = 0
with open(tmp, "wb") as f:
for obj in objects:
href, headers = _resolve_oid(session, base_url, obj["oid"], int(obj["size"]), auth)
obj_auth = None if headers.get("Authorization") else auth
with session.get(href, headers=headers, stream=True, timeout=120, auth=obj_auth) as r:
r.raise_for_status()
for chunk in r.iter_content(CHUNK):
f.write(chunk)
digest.update(chunk)
got += len(chunk)
if progress_cb is not None and total:
progress_cb(min(1.0, got / total))
if total and got != total:
raise RuntimeError(f"size mismatch: {got}/{total} bytes")
if sha256 and digest.hexdigest() != sha256:
raise RuntimeError("sha256 mismatch")
os.replace(tmp, dst)
return dst
except Exception as e:
last_error = e
try:
os.remove(tmp)
except OSError:
pass
raise RuntimeError(f"model bundle download failed: {last_error}")

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": "b7ae194bc219d344d92e77e7ad56232c48e75bab4d0bdcb2ebf20dccc7ba2c4d", "sha256": "c6ff7efa8f20decbd9ee75a2b11d6be6b2b0888b45d90dce65ac3a1615e3afc5",
"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": "447634f6ee186df629e3fc03556fab3222d84037da0e9061aa44e7238b0f9b5e", "sha256": "60bb1b539739e75249c3034ff505aeb9bf38d6fcf8d7f713d61395104bee0002",
"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": "5a69a9967bdeaf8258b26487be40420c8f37935161699527f2bafc3ddfa5e710", "sha256": "7111179a66be1ac8bbcefcd2c6f836150e9e31b01d36244315203123f980beb8",
"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": "24785c0b8eee854e1da2f03ee961de6650fcd7b975797c45d5d1d8ea995b8273", "sha256": "16bec7e29dc9b07b743d523735eb3adfd5b43b0ffa7cc89d189ee050065aa08d",
"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": "37042bfe3e87db35e8abb8a03562c90be5ce835e832b2b4159f57e6f2643764e", "sha256": "8eaf6bc52ad788587e3a9bfbd06246f68931c8a151772e37c895c396001e5243",
"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": "9818f5eb5c850cdee9ec6d62d239de0451659cd62b891c01083433f41ca8dbde", "sha256": "c785e13bf86fcfdc5bf0d5e5acc6a49b764f800e4a5e87bb740cd187fdeb4584",
"size": 67864 "size": 67864
}, },
"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": "6e6c9e9e66579d1f03861f1535da3a960f53f50e734a6030b41b9d8f8516c981", "sha256": "d98244f8f9b3b1e1f412802d93095a0a05dedf5c6a98c9cefbee6c27e999d341",
"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": "ca16d91129cf70227a92b81dd4204760681fc6f2ca7bd9ed84e58b7cd2efd2de", "sha256": "9df118ff8a45497e258ac4cf520db3c80f74b8cd22a41b54bba26d372bc6a8e5",
"size": 340000 "size": 340000
}, },
"runtime": { "runtime": {
@@ -228,13 +228,13 @@
} }
}, },
"signatures": { "signatures": {
"python/iqpilot_private/models/__init__.cpython-312-aarch64-linux-gnu.so": "lSYzYS2zoycLPzqpLu8RE5hnpVFgSA0e6ZXgH57Ve2f3jwV9OSCbM+JHqxy5fDDiPW1ksRM6j01vFE+6SY5hCw==", "python/iqpilot_private/models/__init__.cpython-312-aarch64-linux-gnu.so": "XBVN/BJBOnlVrPJ0pq/+rAkGwSDkJk3t29YpPaYsWCQPWD0JADTip4a8vj0GvCQgXdGwNsMJb2b7Cu6TeoOOAw==",
"python/iqpilot_private/models/big_catalog.cpython-312-aarch64-linux-gnu.so": "WHHAjMetD6q1tsUdG54ulOLADQImspgfqr7p7lqPp0NMmvy+MYVJLQPmxrjrSokZTwtGivlLNEy8wo4EZ6OuCQ==", "python/iqpilot_private/models/big_catalog.cpython-312-aarch64-linux-gnu.so": "zhE70qcpwK9vSvqYp7fKuMptsV2JMMpmsnUeyFuA89mtFDKzUoJi+jxwOsvcH1b0qk6CuemH86l0/UpA0AbRAg==",
"python/iqpilot_private/models/egpu_model.cpython-312-aarch64-linux-gnu.so": "+QTJvOd9zd1UfXcwn0+EhoMHhGMvPyBOB59GglVi5fx9j1TUqDWu00Ip/30IlbxWTbTMZQzDNFWR3puQZ/tgCA==", "python/iqpilot_private/models/egpu_model.cpython-312-aarch64-linux-gnu.so": "4JFNjPPV2i8xPXrWrwpkuOWPDRCD/m0+bVGgWO9TEmFLKbAu+RQslAHesptxfDAyomJbeRLBvXk1KVRaTzQ+CA==",
"python/iqpilot_private/models/emac_model_meta.cpython-312-aarch64-linux-gnu.so": "e9ZmfuFD/FSWPAt/9nmyFafkGojmCuRxmaHixSCMPLaFVDrwwomuDoaq4ogaQ6HmHDdCosBkNh7w/6ZIHgpzDg==", "python/iqpilot_private/models/emac_model_meta.cpython-312-aarch64-linux-gnu.so": "Q1luLZ5apcCJ3Im8Py3uP0D42VYXdsw5y48FAtofFrCQJlZ+LTPshjSVbu2GYQnxny9fFt4J6DT9zvMtLheqAA==",
"python/iqpilot_private/models/fetcher.cpython-312-aarch64-linux-gnu.so": "ImIktR4cuqUzWtoZe1nSA0sIygmpuVW76jNZwerd+qG1qmJojkW7mfQzZ6yiwRTV51FybjLjLzhZgAA5kqY9Cw==", "python/iqpilot_private/models/fetcher.cpython-312-aarch64-linux-gnu.so": "YPc7Ix5gW6YCQX2DrpL5bWu5RKMIOCJvaR/xDwTJmo3/dfkgvfAv7jG41rTxpXaRl8rM8ipUoPI8tlKGzucqDw==",
"python/iqpilot_private/models/git_auth.cpython-312-aarch64-linux-gnu.so": "qGUpnjJFC67qdIuc3JeTtDHVKRuK9oEe59f/43IJF3avDTzbFTHnLWM3AfvO+j3i/m4vuuRjAlKVCQEFRlNWDA==", "python/iqpilot_private/models/git_auth.cpython-312-aarch64-linux-gnu.so": "R6WdFIgsLn4/8fq5qSU64ye7n+Ql9tr3Vhy4gYKIGKWY5GkXplkc8KMA1ciO7yy0n6Qw5HPCmGKrAbkQerrfBw==",
"python/iqpilot_private/models/helpers.cpython-312-aarch64-linux-gnu.so": "rB3MDS2Ue0p8889DbKn+gpzyaM1IeR4cb38uy3taitPwV4Em2Zbyr47W2hbvlKL32m2pBWwiJCM8EeqlafEtDg==", "python/iqpilot_private/models/helpers.cpython-312-aarch64-linux-gnu.so": "BoA4zydMAnpLyAFxzjwvggxB8Hzx+OJd3puAAckzi0UiiGhdiTGl9jexUcGpOolST3pPCRSoCXT0fVihW/OFDw==",
"python/iqpilot_private/models/manager.cpython-312-aarch64-linux-gnu.so": "qsLTZana4Y5dxS0m8gGvtYVTFeXixkPUawT2SGwQF8ccxRQogAUt1bQJfJLZA4xmOnTG2Da0qhxFQ13VsFBeCA==" "python/iqpilot_private/models/manager.cpython-312-aarch64-linux-gnu.so": "8VccT47QBNee1ZcnKSsHchV10dOJP0q55FtabpSZr+azLP+hmWiClG3j9VqRe5ctAzuMa53m7eSRjXiTKCpwDw=="
} }
} }

View File

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

View File

@@ -0,0 +1 @@
44200007e6d1ccd1e6016db08abf3f54d733913f

View File

@@ -599,6 +599,8 @@ struct IQNavState @0xaae9afb364368cd9 {
cameraChime @59 :Bool; cameraChime @59 :Bool;
cameraTrusted @60 :Bool; cameraTrusted @60 :Bool;
cameraSourceAge @61 :Float32; cameraSourceAge @61 :Float32;
mapboxSpeedLimit @62 :Float32;
mapboxSpeedLimitValid @63 :Bool;
enum CameraType { enum CameraType {
none @0; none @0;

View File

@@ -199,6 +199,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"ModelRunnerTypeCache", {CLEAR_ON_ONROAD_TRANSITION, INT}}, {"ModelRunnerTypeCache", {CLEAR_ON_ONROAD_TRANSITION, INT}},
{"ForceOnroadUntil", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, INT, "0"}}, {"ForceOnroadUntil", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, INT, "0"}},
{"IQAlwaysOffroad", {CLEAR_ON_MANAGER_START, BOOL}}, {"IQAlwaysOffroad", {CLEAR_ON_MANAGER_START, BOOL}},
{"IQBenchIgnition", {PERSISTENT, BOOL, "0"}},
{"Offroad_TiciSupport", {CLEAR_ON_MANAGER_START, JSON}}, {"Offroad_TiciSupport", {CLEAR_ON_MANAGER_START, JSON}},
{"OnroadScreenOffBrightness", {PERSISTENT, INT, "0"}}, {"OnroadScreenOffBrightness", {PERSISTENT, INT, "0"}},
{"OnroadScreenOffTimer", {PERSISTENT, INT, "15"}}, {"OnroadScreenOffTimer", {PERSISTENT, INT, "15"}},
@@ -241,6 +242,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"MacModelReachable", {CLEAR_ON_MANAGER_START, BOOL}}, {"MacModelReachable", {CLEAR_ON_MANAGER_START, BOOL}},
{"MacModelReady", {CLEAR_ON_MANAGER_START, BOOL}}, {"MacModelReady", {CLEAR_ON_MANAGER_START, BOOL}},
{"MacModelActive", {CLEAR_ON_MANAGER_START, BOOL}}, {"MacModelActive", {CLEAR_ON_MANAGER_START, BOOL}},
{"MacModelFault", {CLEAR_ON_MANAGER_START, BOOL}},
{"MacModelFailed", {CLEAR_ON_MANAGER_START, BOOL}}, {"MacModelFailed", {CLEAR_ON_MANAGER_START, BOOL}},
{"MacModelLastError", {CLEAR_ON_MANAGER_START, STRING}}, {"MacModelLastError", {CLEAR_ON_MANAGER_START, STRING}},
{"MacModelLatencyMs", {CLEAR_ON_MANAGER_START, FLOAT, "0.0"}}, {"MacModelLatencyMs", {CLEAR_ON_MANAGER_START, FLOAT, "0.0"}},
@@ -251,6 +253,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"IQEgpuDisabled", {PERSISTENT, BOOL, "0"}}, {"IQEgpuDisabled", {PERSISTENT, BOOL, "0"}},
{"UsbGpuPresent", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}}, {"UsbGpuPresent", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
{"UsbGpuCompiled", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}}, {"UsbGpuCompiled", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
{"UsbGpuReady", {PERSISTENT, BOOL, "0"}},
{"UsbGpuLoading", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION | CLEAR_ON_IGNITION_ON, BOOL}}, {"UsbGpuLoading", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION | CLEAR_ON_IGNITION_ON, BOOL}},
{"UsbGpuActive", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION | CLEAR_ON_IGNITION_ON, BOOL}}, {"UsbGpuActive", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION | CLEAR_ON_IGNITION_ON, BOOL}},
{"UsbGpuFailed", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION | CLEAR_ON_IGNITION_ON, BOOL}}, {"UsbGpuFailed", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION | CLEAR_ON_IGNITION_ON, BOOL}},
@@ -319,6 +322,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"MacModelCompiled", {CLEAR_ON_MANAGER_START, BOOL}}, {"MacModelCompiled", {CLEAR_ON_MANAGER_START, BOOL}},
{"MacModelReady", {CLEAR_ON_MANAGER_START, BOOL}}, {"MacModelReady", {CLEAR_ON_MANAGER_START, BOOL}},
{"MacModelActive", {CLEAR_ON_MANAGER_START, BOOL}}, {"MacModelActive", {CLEAR_ON_MANAGER_START, BOOL}},
{"MacModelFault", {CLEAR_ON_MANAGER_START, BOOL}},
{"MacModelFailed", {CLEAR_ON_MANAGER_START, BOOL}}, {"MacModelFailed", {CLEAR_ON_MANAGER_START, BOOL}},
{"MacModelLastError", {CLEAR_ON_MANAGER_START, STRING}}, {"MacModelLastError", {CLEAR_ON_MANAGER_START, STRING}},
{"MacModelDownloadProgress", {CLEAR_ON_MANAGER_START, FLOAT}}, {"MacModelDownloadProgress", {CLEAR_ON_MANAGER_START, FLOAT}},

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

View File

@@ -777,7 +777,15 @@ class SpeedLimitController:
self.segment_distance = 0.0 self.segment_distance = 0.0
self.tomtom_segment_distance = 0.0 self.tomtom_segment_distance = 0.0
online_limit = self.tomtom_limit if self.tomtom_limit > 0 else self.mapbox_limit nav_mapbox_limit = 0.0
if getattr(sm, "alive", {}).get("iqNavState", False) and getattr(sm, "valid", {}).get("iqNavState", False):
nav_state = sm["iqNavState"]
if getattr(nav_state, "mapboxSpeedLimitValid", False):
candidate = float(getattr(nav_state, "mapboxSpeedLimit", 0.0))
if math.isfinite(candidate) and candidate >= LIMIT_MIN_SPEED:
nav_mapbox_limit = candidate
mapbox_limit = nav_mapbox_limit if nav_mapbox_limit > 0 else self.mapbox_limit
online_limit = self.tomtom_limit if self.tomtom_limit > 0 else mapbox_limit
dashboard_limit = float(dashboard_speed_limit) if dashboard_speed_limit else 0.0 dashboard_limit = float(dashboard_speed_limit) if dashboard_speed_limit else 0.0
resolved_limit, resolved_source = self._resolver.resolve(dashboard_limit, online_limit, slc_params) resolved_limit, resolved_source = self._resolver.resolve(dashboard_limit, online_limit, slc_params)

View File

@@ -663,3 +663,51 @@ def test_construction_zone_fires_event_once_per_zone_entry():
assert event not in controller.pending_events assert event not in controller.pending_events
controller.update_limits(0.0, None, True, 33.0, 30.0, _construction_sm(), slc_params) controller.update_limits(0.0, None, True, 33.0, 30.0, _construction_sm(), slc_params)
assert event in controller.pending_events assert event in controller.pending_events
@pytest.mark.parametrize("alive,valid,limit_valid,limit", [
(False, True, True, 25.0), (True, False, True, 25.0), (True, True, False, 25.0),
(True, True, True, 0.0), (True, True, True, float("nan")), (True, True, True, float("inf")),
])
def test_navigation_mapbox_limit_requires_fresh_valid_data(alive, valid, limit_valid, limit):
controller = _construction_controller()
controller.get_tomtom_speed_limit = lambda *_args: None
controller.mapbox_limit = 20.0
sm = _FakeSM(_build_sm())
sm["iqNavState"] = custom.IQNavState.new_message(mapboxSpeedLimit=limit, mapboxSpeedLimitValid=limit_valid)
sm.alive["iqNavState"] = alive
sm.valid = {"iqNavState": valid}
controller.update_limits(0, datetime.now(), True, 30, 20, sm, _base_slc_params_controller())
assert controller.target == pytest.approx(20.0)
assert controller.source == "Mapbox"
@pytest.mark.parametrize("policy,expected", [(0, 0.0), (1, 25.0), (2, 25.0)])
@pytest.mark.parametrize("online_filler", [False, True])
def test_navigation_mapbox_only_limit_obeys_slc_policy(policy, expected, online_filler):
controller = _construction_controller()
controller.get_tomtom_speed_limit = lambda *_args: None
sm = _FakeSM(_build_sm())
sm["iqNavState"] = custom.IQNavState.new_message(mapboxSpeedLimit=25.0, mapboxSpeedLimitValid=True)
sm.alive["iqNavState"] = True
sm.valid = {"iqNavState": True}
params = _base_slc_params_controller() | {"slc_policy": policy, "slc_online_filler": online_filler}
controller.update_limits(0, datetime.now(), True, 30, 20, sm, params)
assert controller.target == pytest.approx(expected)
def test_navigation_mapbox_limit_requires_confirmation_before_override(set_speed_slc):
system = set_speed_slc
system.params["speed_limit_confirmation_higher"] = True
system.slc.slc._resolver.map_speed_limit = 0
system.sm["iqNavState"] = custom.IQNavState.new_message(mapboxSpeedLimit=60 * system.unit, mapboxSpeedLimitValid=True)
system.sm.alive["iqNavState"] = True
system.sm.valid = {"iqNavState": True}
system.step(50, new_gesture=True)
assert system.slc.assist_state == custom.IQPlan.SpeedLimit.AssistState.preActive
assert system.step(70, increase=True) == pytest.approx(50)
system.sm["carState"].buttonEvents = [car.CarState.ButtonEvent(type="accelCruise", pressed=False)]
assert system.step(71, increase=True) == pytest.approx(60)
system.sm["carState"].buttonEvents = []
assert system.step(72, increase=True) == pytest.approx(60)
assert system.step(73, increase=True, new_gesture=True) == pytest.approx(73)

View File

@@ -57,6 +57,11 @@ def egpu_pkl_path(meta: dict) -> str:
return os.path.join(Paths.model_root(), f"egpu_{meta['key']}_{meta['sha256'][:8]}_amd_tinygrad.pkl") return os.path.join(Paths.model_root(), f"egpu_{meta['key']}_{meta['sha256'][:8]}_amd_tinygrad.pkl")
def egpu_policy_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.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")
@@ -137,6 +142,15 @@ 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:
art = meta.get("egpu_policy_artifact" if policy else "egpu_artifact")
if not art or not art.get("objects"):
return None
from iqpilot.selfdrive.iqmodeld.model_bundle_downloader import download_lfs_bundle
dest = egpu_policy_pkl_path(meta) if policy else egpu_pkl_path(meta)
return download_lfs_bundle(art["objects"], dest, art["sha256"], int(art.get("size", 0)), progress_cb=progress_cb)
def patch_tinygrad_fetch_fw() -> None: def patch_tinygrad_fetch_fw() -> None:
import pathlib import pathlib

View File

@@ -5,6 +5,7 @@ from __future__ import annotations
import numpy as np import numpy as np
from iqpilot.selfdrive.iqmodeld.egpu_policy import PolicyRunner
from iqpilot.selfdrive.iqmodeld.temporal_state import MODEL_INPUT_SPEC, TemporalInputState, spec_from_meta from iqpilot.selfdrive.iqmodeld.temporal_state import MODEL_INPUT_SPEC, TemporalInputState, spec_from_meta
@@ -25,13 +26,17 @@ class EgpuPipeline:
def run(self, warped: np.ndarray, desire_vec: np.ndarray, traffic_convention: np.ndarray, def run(self, warped: np.ndarray, desire_vec: np.ndarray, traffic_convention: np.ndarray,
action_t: np.ndarray) -> np.ndarray: action_t: np.ndarray) -> np.ndarray:
inputs = self.state.push_and_materialize(warped, desire_vec, traffic_convention, action_t) if isinstance(self.infer_fn, PolicyRunner):
out = np.asarray(self.infer_fn(inputs), dtype=np.float32).reshape(-1) out = np.asarray(self.infer_fn.run(warped, desire_vec, traffic_convention, action_t), dtype=np.float32).reshape(-1)
else:
inputs = self.state.push_and_materialize(warped, desire_vec, traffic_convention, action_t)
out = np.asarray(self.infer_fn(inputs), dtype=np.float32).reshape(-1)
if out.shape[0] != self.output_len: if out.shape[0] != self.output_len:
raise EgpuPipelineError(f"eGPU output length {out.shape[0]} != {self.output_len}") raise EgpuPipelineError(f"eGPU output length {out.shape[0]} != {self.output_len}")
if not np.isfinite(out).all(): if not np.isfinite(out).all():
raise EgpuPipelineError("eGPU output contains non-finite values") raise EgpuPipelineError("eGPU output contains non-finite values")
self.state.note_hidden_state(out, self.hidden_slice) if not isinstance(self.infer_fn, PolicyRunner):
self.state.note_hidden_state(out, self.hidden_slice)
return out return out

View File

@@ -0,0 +1,117 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
from __future__ import annotations
import math
import numpy as np
POLICY_FORMAT = 2
QUEUE_NAMES = ("img_q", "big_img_q", "feat_q", "desire_q")
PACKED_ORDER = ("desire", "traffic_convention", "action_t", "prev_feat")
def packed_layout(input_spec: dict) -> tuple[dict[str, tuple[int, ...]], list[int]]:
dp = input_spec["desire_pulse"][0]
fb = input_spec["features_buffer"][0]
shapes = {
"desire": (dp[2],),
"traffic_convention": tuple(input_spec["traffic_convention"][0]),
"action_t": tuple(input_spec["action_t"][0]),
"prev_feat": (fb[0], math.prod(fb[2:])),
}
return shapes, [math.prod(s) for s in shapes.values()]
def queue_shapes(input_spec: dict, frame_skip: int) -> dict[str, tuple[tuple[int, ...], str]]:
img = input_spec["img"][0]
fb = input_spec["features_buffer"][0]
dp = input_spec["desire_pulse"][0]
n_frames = img[1] // 6
img_buf = (frame_skip * (n_frames - 1) + 1, 6, img[2], img[3])
return {
"img_q": (img_buf, "uint8"),
"big_img_q": (img_buf, "uint8"),
"feat_q": ((frame_skip * fb[1], fb[0], math.prod(fb[2:])), "float32"),
"desire_q": ((frame_skip * dp[1], dp[0], dp[2]), "float32"),
}
def make_queues(input_spec: dict, frame_skip: int, device: str) -> dict:
from tinygrad.tensor import Tensor
return {name: Tensor(np.zeros(shape, dtype=dtype), device=device).contiguous().realize()
for name, (shape, dtype) in queue_shapes(input_spec, frame_skip).items()}
class PackedInputs:
def __init__(self, input_spec: dict):
from tinygrad.tensor import Tensor
self.shapes, self.sizes = packed_layout(input_spec)
self.array = np.zeros(sum(self.sizes), dtype=np.float32)
self.views = dict(zip(self.shapes, [v.reshape(s) for s, v in zip(self.shapes.values(), np.split(self.array, np.cumsum(self.sizes[:-1])))], strict=True))
self.tensor = Tensor(self.array, device="NPY").realize()
def make_run_policy(model_runner, input_spec: dict, frame_skip: int, device: str):
from tinygrad.tensor import Tensor
shapes, sizes = packed_layout(input_spec)
fb = input_spec["features_buffer"][0]
def shift_and_sample(buf, new_val, sample_fn):
buf.assign(buf[1:].cat(new_val, dim=0).contiguous())
return sample_fn(buf)
def sample_skip(buf):
return buf[::frame_skip].contiguous().flatten(0, 1).unsqueeze(0)
def sample_desire(buf):
return buf.reshape(-1, frame_skip, *buf.shape[1:]).max(1).flatten(0, 1).unsqueeze(0)
def run_policy(warped, img_q, big_img_q, feat_q, desire_q, packed_npy_inputs):
packed_npy_inputs = packed_npy_inputs.to(device)
warped = warped.to(device)
Tensor.realize(packed_npy_inputs, warped)
img = shift_and_sample(img_q, warped[0:1], sample_skip)
big_img = shift_and_sample(big_img_q, warped[1:2], sample_skip)
desire, traffic_convention, action_t, prev_feat = (t.reshape(s) for t, s in zip(packed_npy_inputs.split(sizes), shapes.values(), strict=True))
desire_buf = shift_and_sample(desire_q, desire.reshape(1, 1, -1), sample_desire)
feat_buf = shift_and_sample(feat_q, prev_feat.reshape(1, 1, -1), sample_skip)
inputs = {
"img": img,
"big_img": big_img,
"features_buffer": feat_buf.reshape(fb),
"desire_pulse": desire_buf,
"traffic_convention": traffic_convention,
"action_t": action_t,
}
out = next(iter(model_runner(inputs).values())).cast("float32")
return out.reshape(-1),
return run_policy
class PolicyRunner:
def __init__(self, jit, input_spec: dict, frame_skip: int, hidden_slice: slice, device: str):
from tinygrad.tensor import Tensor
self._Tensor = Tensor
self._jit = jit
self._queues = make_queues(input_spec, frame_skip, device)
self._packed = PackedInputs(input_spec)
self._hidden = hidden_slice
self._prev_desire = np.zeros(input_spec["desire_pulse"][0][2], dtype=np.float32)
self._warped_shape = (2, 6, *input_spec["img"][0][2:])
def run(self, warped: np.ndarray, desire_pulse: np.ndarray, traffic_convention: np.ndarray,
action_t: np.ndarray) -> np.ndarray:
cur = desire_pulse.astype(np.float32, copy=False)
v = self._packed.views
v["desire"][:] = np.where(cur - self._prev_desire > 0.99, cur, 0)
self._prev_desire[:] = cur
v["traffic_convention"][:] = np.asarray(traffic_convention, dtype=np.float32).reshape(v["traffic_convention"].shape)
v["action_t"][:] = np.asarray(action_t, dtype=np.float32).reshape(v["action_t"].shape)
warped_t = self._Tensor(np.ascontiguousarray(warped, dtype=np.uint8).reshape(self._warped_shape), device="NPY").realize()
out, = self._jit(warped=warped_t, packed_npy_inputs=self._packed.tensor, **self._queues)
flat = out.numpy().reshape(-1)
v["prev_feat"][:] = flat[self._hidden].reshape(v["prev_feat"].shape)
return flat

View File

@@ -38,8 +38,8 @@ 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, egpu_pkl_path, egpu_present_consented, egpu_selected, local_onnx, patch_tinygrad_fetch_fw, download_onnx, download_precompiled, egpu_pkl_path, egpu_policy_pkl_path, egpu_present_consented, egpu_selected, local_onnx,
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
from iqpilot.selfdrive.iqmodeld.egpu_pipeline import EgpuPipeline, EgpuPipelineError, make_big_channel_payload from iqpilot.selfdrive.iqmodeld.egpu_pipeline import EgpuPipeline, EgpuPipelineError, make_big_channel_payload
@@ -47,6 +47,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.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
@@ -89,9 +90,10 @@ def _wait_for_egpu(params: Params) -> None:
def _compile_in_subprocess(meta: dict, onnx_path: str, pkl_path: str) -> None: def _compile_in_subprocess(meta: dict, onnx_path: str, pkl_path: str) -> None:
cmd = [sys.executable, "-m", "iqpilot.selfdrive.iqmodeld.tools.compile_egpu_model", cmd = [sys.executable, "-m", "iqpilot.selfdrive.iqmodeld.tools.compile_egpu_model",
"--model", meta["key"], "--onnx", onnx_path, "--output", pkl_path] "--model", meta["key"], "--onnx", onnx_path, "--output", pkl_path,
"--progress-param", "UsbGpuSetupProgress", "--progress-base", "0.5", "--progress-span", "0.48"]
compile_env = {**os.environ, "DEV": "USB+AMD:LLVM", "FLOAT16": "1", compile_env = {**os.environ, "DEV": "USB+AMD:LLVM", "FLOAT16": "1",
"JIT_BATCH_SIZE": "0", "GMMU": "0"} "JIT_BATCH_SIZE": "0", "GMMU": "0", "TC_OPT": "2"}
proc = subprocess.run(cmd, timeout=COMPILE_TIMEOUT_S, capture_output=True, text=True, proc = subprocess.run(cmd, timeout=COMPILE_TIMEOUT_S, capture_output=True, text=True,
env=compile_env, preexec_fn=lambda: os.nice(20)) env=compile_env, preexec_fn=lambda: os.nice(20))
if proc.returncode != 0: if proc.returncode != 0:
@@ -99,12 +101,43 @@ def _compile_in_subprocess(meta: dict, onnx_path: str, pkl_path: str) -> None:
raise RuntimeError(f"eGPU model compile failed (rc={proc.returncode}): {tail}") raise RuntimeError(f"eGPU model compile failed (rc={proc.returncode}): {tail}")
_precompiled_tried = False
def _ensure_artifact(params: Params, meta: dict) -> str: def _ensure_artifact(params: Params, meta: dict) -> str:
pkl_path = egpu_pkl_path(meta) global _precompiled_tried
if os.path.isfile(pkl_path): policy_path = egpu_policy_pkl_path(meta)
return pkl_path if os.path.isfile(policy_path):
return policy_path
legacy_path = egpu_pkl_path(meta)
params.put_bool("UsbGpuCompiled", False) params.put_bool("UsbGpuCompiled", False)
params.put_bool("UsbGpuReady", False)
if meta.get("egpu_policy_artifact") and not _precompiled_tried:
_precompiled_tried = True
params.put("UsbGpuSetupProgress", "0.0")
dl_last = [-1.0]
def _dl_prog(p: float) -> None:
if p - dl_last[0] >= 0.02 or p >= 1.0:
dl_last[0] = p
params.put("UsbGpuSetupProgress", f"{p:.3f}")
try:
cloudlog.warning(f"iqegpumodeld downloading precompiled {meta['key']} policy "
f"({int(meta['egpu_policy_artifact'].get('size', 0)) / 1e6:.0f}MB)")
precompiled = download_precompiled(meta, progress_cb=_dl_prog, policy=True)
if precompiled is not None:
cloudlog.warning(f"iqegpumodeld precompiled ready -> {precompiled}")
return precompiled
except Exception as e:
cloudlog.warning(f"iqegpumodeld precompiled policy unavailable ({e}); falling back")
if os.path.isfile(legacy_path):
cloudlog.warning(f"iqegpumodeld using legacy per-tensor artifact {legacy_path}; policy artifact not hosted yet")
return legacy_path
onnx_path = local_onnx(meta) onnx_path = local_onnx(meta)
if onnx_path is None: if onnx_path is None:
params.put("UsbGpuSetupProgress", "0.0") params.put("UsbGpuSetupProgress", "0.0")
@@ -114,14 +147,14 @@ def _ensure_artifact(params: Params, meta: dict) -> str:
def _prog(p: float) -> None: def _prog(p: float) -> None:
if p - last[0] >= 0.02 or p >= 1.0: if p - last[0] >= 0.02 or p >= 1.0:
last[0] = p last[0] = p
params.put("UsbGpuSetupProgress", f"{p:.3f}") params.put("UsbGpuSetupProgress", f"{p * 0.5:.3f}")
onnx_path = download_onnx(meta, progress_cb=_prog) onnx_path = download_onnx(meta, progress_cb=_prog)
cloudlog.warning(f"iqegpumodeld compiling {meta['key']} for USB-AMD (one-time, can take minutes)") cloudlog.warning(f"iqegpumodeld compiling {meta['key']} for USB-AMD (one-time, can take minutes)")
_compile_in_subprocess(meta, onnx_path, pkl_path) _compile_in_subprocess(meta, onnx_path, policy_path)
cloudlog.warning(f"iqegpumodeld compiled -> {pkl_path}") cloudlog.warning(f"iqegpumodeld compiled -> {policy_path}")
return pkl_path return policy_path
def _load_infer_fn(pkl_path: str, meta: dict): def _load_infer_fn(pkl_path: str, meta: dict):
@@ -136,6 +169,10 @@ def _load_infer_fn(pkl_path: str, meta: dict):
if int(bundle.get("output_len", -1)) != int(meta["output_len"]): if int(bundle.get("output_len", -1)) != int(meta["output_len"]):
quarantine_artifact(pkl_path, "pkl output_len mismatch") quarantine_artifact(pkl_path, "pkl output_len mismatch")
raise RuntimeError(f"artifact output_len {bundle.get('output_len')} != {meta['output_len']}") raise RuntimeError(f"artifact output_len {bundle.get('output_len')} != {meta['output_len']}")
if bundle.get("format") == POLICY_FORMAT:
runner = PolicyRunner(bundle["run_policy"], bundle["input_spec"], int(bundle["frame_skip"]),
meta["output_slices"]["hidden_state"], bundle.get("input_device", "AMD"))
return runner, bundle["input_spec"]
jit = bundle["run_model"] jit = bundle["run_model"]
input_dev = bundle.get("input_device", "AMD") input_dev = bundle.get("input_device", "AMD")
input_spec = bundle["input_spec"] input_spec = bundle["input_spec"]
@@ -152,7 +189,12 @@ def _load_infer_fn(pkl_path: str, meta: dict):
def _warmup(infer_fn, input_spec: dict, output_len: int) -> float: def _warmup(infer_fn, input_spec: dict, output_len: int) -> float:
zeros = {name: np.zeros(shape, dtype=dtype) for name, (shape, dtype) in input_spec.items()} zeros = {name: np.zeros(shape, dtype=dtype) for name, (shape, dtype) in input_spec.items()}
t0 = time.perf_counter() t0 = time.perf_counter()
out = infer_fn(zeros) if isinstance(infer_fn, PolicyRunner):
img = input_spec["img"][0]
out = infer_fn.run(np.zeros((2, 6, img[2], img[3]), dtype=np.uint8), np.zeros(input_spec["desire_pulse"][0][2], dtype=np.float32),
np.zeros(2, dtype=np.float32), np.zeros(2, dtype=np.float32))
else:
out = infer_fn(zeros)
dt = time.perf_counter() - t0 dt = time.perf_counter() - t0
if out.shape[0] != output_len or not np.isfinite(out).all(): if out.shape[0] != output_len or not np.isfinite(out).all():
raise RuntimeError(f"warmup produced invalid output (len={out.shape[0]})") raise RuntimeError(f"warmup produced invalid output (len={out.shape[0]})")
@@ -207,6 +249,7 @@ def main(demo: bool = False) -> None:
params.put_bool("UsbGpuLoading", False) params.put_bool("UsbGpuLoading", False)
params.put_bool("UsbGpuCompiled", True) params.put_bool("UsbGpuCompiled", True)
params.put_bool("UsbGpuReady", True)
params.put("UsbGpuSetupProgress", "1.0") params.put("UsbGpuSetupProgress", "1.0")
cloudlog.warning(f"iqegpumodeld model: {meta['key']} ({meta['model_name']})") cloudlog.warning(f"iqegpumodeld model: {meta['key']} ({meta['model_name']})")
cloudlog.warning(f"iqegpumodeld model up (warmup {warm_s * 1e3:.0f}ms)") cloudlog.warning(f"iqegpumodeld model up (warmup {warm_s * 1e3:.0f}ms)")

View File

@@ -0,0 +1,87 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
from __future__ import annotations
import hashlib
import json
import os
MODELS_BASE_URLS = (
"https://git.konn3kt.com/teal/IQModels/raw/branch/main",
"https://gitlvb.teallvbs.xyz/teal/IQModels/raw/branch/main",
)
CHUNK = 4 * 1024 * 1024
HTTP_TIMEOUT_S = 60.0
STREAM_RETRIES = 6
def _requests_auth():
import importlib
for mod in ("iqpilot_private.models.git_auth", "iqpilot.models_private_src.git_auth",
"iqpilot.selfdrive.iqmodeld.models.git_auth"):
try:
return importlib.import_module(mod).get_requests_auth()
except Exception:
continue
return None
def _lfs_endpoint(base_url: str) -> str:
return base_url.split("/raw/", 1)[0] + ".git/info/lfs"
def _resolve_oid(session, base_url: str, oid: str, size: int, auth):
import requests
batch = session.post(f"{_lfs_endpoint(base_url)}/objects/batch",
data=json.dumps({"operation": "download", "transfers": ["basic"],
"objects": [{"oid": oid, "size": size}]}),
headers={"Content-Type": "application/vnd.git-lfs+json",
"Accept": "application/vnd.git-lfs+json"},
auth=auth, timeout=HTTP_TIMEOUT_S)
batch.raise_for_status()
entry = batch.json()["objects"][0]
if "actions" not in entry:
raise requests.RequestException(f"LFS object unavailable: {entry.get('error', oid)}")
action = entry["actions"]["download"]
return action["href"], action.get("header", {})
def download_lfs_bundle(objects: list, dst: str, sha256: str, size: int, progress_cb=None) -> str:
import requests
auth = _requests_auth()
session = requests.Session()
os.makedirs(os.path.dirname(dst), exist_ok=True)
tmp = dst + ".part"
total = int(size) or sum(int(o["size"]) for o in objects)
last_error: Exception | None = None
for base_url in MODELS_BASE_URLS:
for attempt in range(STREAM_RETRIES):
try:
digest = hashlib.sha256()
got = 0
with open(tmp, "wb") as f:
for obj in objects:
href, headers = _resolve_oid(session, base_url, obj["oid"], int(obj["size"]), auth)
obj_auth = None if headers.get("Authorization") else auth
with session.get(href, headers=headers, stream=True, timeout=120, auth=obj_auth) as r:
r.raise_for_status()
for chunk in r.iter_content(CHUNK):
f.write(chunk)
digest.update(chunk)
got += len(chunk)
if progress_cb is not None and total:
progress_cb(min(1.0, got / total))
if total and got != total:
raise RuntimeError(f"size mismatch: {got}/{total} bytes")
if sha256 and digest.hexdigest() != sha256:
raise RuntimeError("sha256 mismatch")
os.replace(tmp, dst)
return dst
except Exception as e:
last_error = e
try:
os.remove(tmp)
except OSError:
pass
raise RuntimeError(f"model bundle download failed: {last_error}")

View File

@@ -0,0 +1,82 @@
"""
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos/
"""
import os
import numpy as np
os.environ["DEV"] = "CPU"
from iqpilot.selfdrive.iqmodeld.egpu_policy import PolicyRunner, make_run_policy, packed_layout, queue_shapes
from iqpilot.selfdrive.iqmodeld.temporal_state import TemporalInputState
SPEC = {
"img": ((1, 12, 8, 16), "uint8"),
"big_img": ((1, 12, 8, 16), "uint8"),
"desire_pulse": ((1, 25, 8), "float32"),
"traffic_convention": ((1, 2), "float32"),
"action_t": ((1, 2), "float32"),
"features_buffer": ((1, 24, 512), "float32"),
}
FS = 4
OUT_LEN = 2580
HIDDEN = slice(1064, 1576)
def _pack(inputs):
from tinygrad.tensor import Tensor
parts = [inputs[k].cast("float32").reshape(-1) for k in ("img", "big_img", "features_buffer", "desire_pulse", "traffic_convention", "action_t")]
flat = Tensor.cat(*parts)
hidden = (flat[:512] * 0.001).reshape(1, 512)
return flat, hidden
def _fake_model(inputs):
from tinygrad.tensor import Tensor
flat, hidden = _pack(inputs)
n = flat.shape[0]
head = flat[:min(n, HIDDEN.start)]
out = Tensor.cat(head.pad((0, HIDDEN.start - head.shape[0])), hidden.reshape(-1), Tensor.zeros(OUT_LEN - HIDDEN.stop, device="CPU"))
return {"outputs": out.reshape(1, -1)}
class _Reference:
def __init__(self):
self.state = TemporalInputState(FS, SPEC)
def run(self, warped, desire, traffic, action_t):
inputs = self.state.push_and_materialize(warped, desire, traffic, action_t)
from tinygrad.tensor import Tensor
t = {k: Tensor(np.ascontiguousarray(v), device="CPU") for k, v in inputs.items()}
out = _fake_model(t)["outputs"].numpy().reshape(-1)
self.state.note_hidden_state(out, HIDDEN)
return out
def test_policy_queues_match_temporal_state():
from tinygrad.engine.jit import TinyJit
jit = TinyJit(make_run_policy(_fake_model, SPEC, FS, "CPU"), prune=True)
runner = PolicyRunner(jit, SPEC, FS, HIDDEN, "CPU")
ref = _Reference()
rng = np.random.default_rng(3)
desire = np.zeros(8, dtype=np.float32)
for i in range(14):
warped = rng.integers(0, 256, (2, 6, 8, 16), dtype=np.int64).astype(np.uint8)
if i in (2, 3, 9):
desire[:] = 0
desire[1 + (i % 3)] = 1
elif i == 5:
desire[:] = 0
traffic = np.array([1.0, 0.0], dtype=np.float32) if i % 2 else np.array([0.0, 1.0], dtype=np.float32)
action_t = np.array([0.1 * i, 0.2], dtype=np.float32)
got = runner.run(warped, desire, traffic, action_t)
want = ref.run(warped, desire, traffic, action_t)
np.testing.assert_array_equal(got, want, err_msg=f"frame {i}")
def test_layouts():
shapes, sizes = packed_layout(SPEC)
assert list(shapes) == ["desire", "traffic_convention", "action_t", "prev_feat"]
assert sum(sizes) == 8 + 2 + 2 + 512
q = queue_shapes(SPEC, FS)
assert q["img_q"][0] == (5, 6, 8, 16) and q["feat_q"][0] == (96, 1, 512) and q["desire_q"][0] == (100, 1, 8)

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 argparse import argparse
import gc
import os import os
import pickle import pickle
import time import time
@@ -12,6 +13,7 @@ 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")
import numpy as np import numpy as np
@@ -24,6 +26,22 @@ INPUT_SPEC = dict(MODEL_INPUT_SPEC)
patch_tinygrad_fetch_fw() patch_tinygrad_fetch_fw()
SEED = 42 SEED = 42
KERNEL_PROGRESS_SCALE = 260.0
def _progress_sampler(param: str, base: float, span: float, stop) -> None:
import math
from iqpilot.common.params import Params
from tinygrad.helpers import GlobalCounters
pm = Params()
last = -1.0
while not stop.wait(0.5):
kernels = float(getattr(GlobalCounters, "kernel_count", 0))
value = base + span * (1.0 - math.exp(-kernels / KERNEL_PROGRESS_SCALE))
if value - last >= 0.01:
last = value
pm.put(param, f"{min(base + span, value):.3f}")
def set_input_spec(meta: dict) -> None: def set_input_spec(meta: dict) -> None:
@@ -81,8 +99,27 @@ def compile_model(meta: dict, onnx_path: str, out_path: str) -> str:
if not np.isfinite(baseline).all(): if not np.isfinite(baseline).all():
raise RuntimeError("compiled model produced non-finite outputs") raise RuntimeError("compiled model produced non-finite outputs")
print("pickle round trip") bundle = {
jit = pickle.loads(pickle.dumps(jit)) "run_model": jit,
"model_key": meta["key"],
"model_sha256": meta["sha256"],
"output_len": int(meta["output_len"]),
"frame_skip": int(meta["frame_skip"]),
"input_spec": {name: (tuple(shape), dtype) for name, (shape, dtype) in INPUT_SPEC.items()},
"input_device": Device.DEFAULT,
}
os.makedirs(os.path.dirname(out_path), exist_ok=True)
tmp = out_path + ".part"
print("serialize")
with open(tmp, "wb") as f:
pickle.dump(bundle, f, protocol=pickle.HIGHEST_PROTOCOL)
del bundle, jit
gc.collect()
print("reload + validate")
with open(tmp, "rb") as f:
jit = pickle.load(f)["run_model"]
if not np.array_equal(_run(jit, SEED), baseline): if not np.array_equal(_run(jit, SEED), baseline):
raise RuntimeError("outputs differ from baseline after pickle round trip") raise RuntimeError("outputs differ from baseline after pickle round trip")
if np.array_equal(_run(jit, SEED + 1), baseline): if np.array_equal(_run(jit, SEED + 1), baseline):
@@ -96,19 +133,94 @@ def compile_model(meta: dict, onnx_path: str, out_path: str) -> str:
from iqpilot.selfdrive.iqmodeld.tools.compile_supercombo import _slice_outputs, _validate_pose_outputs from iqpilot.selfdrive.iqmodeld.tools.compile_supercombo import _slice_outputs, _validate_pose_outputs
_validate_pose_outputs(PhaseParser().parse_vision_outputs(_slice_outputs(flat, meta["output_slices"]))) _validate_pose_outputs(PhaseParser().parse_vision_outputs(_slice_outputs(flat, meta["output_slices"])))
os.replace(tmp, out_path)
return out_path
def _policy_frame(seed: int, input_spec: dict):
from tinygrad.tensor import Tensor
rng = np.random.default_rng(seed)
img = input_spec["img"][0]
warped = Tensor(rng.integers(0, 256, (2, 6, img[2], img[3])).astype(np.uint8), device="NPY").realize()
return warped
def compile_policy_model(meta: dict, onnx_path: str, out_path: str) -> str:
from tinygrad.device import Device
from tinygrad.engine.jit import TinyJit
from tinygrad.nn.onnx import OnnxRunner
from iqpilot.selfdrive.iqmodeld.egpu_policy import POLICY_FORMAT, PackedInputs, make_queues, make_run_policy
if meta.get("split"):
raise RuntimeError(f"model {meta['key']} is a split model; eGPU compiles fused models only")
input_spec = {name: (tuple(shape), dtype) for name, (shape, dtype) in INPUT_SPEC.items()}
frame_skip = int(meta["frame_skip"])
device = Device.DEFAULT
jit = TinyJit(make_run_policy(OnnxRunner(onnx_path), input_spec, frame_skip, device), prune=True)
queues = make_queues(input_spec, frame_skip, device)
packed = PackedInputs(input_spec)
def step(seed: int) -> np.ndarray:
packed.views["traffic_convention"][:] = [1, 0]
packed.views["action_t"][:] = [0.2, 0.3]
st = time.perf_counter()
out, = jit(warped=_policy_frame(seed, input_spec), packed_npy_inputs=packed.tensor, **queues)
flat = out.numpy().reshape(-1)
print(f" policy step(seed={seed}) {(time.perf_counter() - st) * 1e3:6.1f} ms")
packed.views["prev_feat"][:] = flat[meta["output_slices"]["hidden_state"]].reshape(packed.views["prev_feat"].shape)
return flat
print("capture + replay")
for i in range(3):
baseline = step(SEED + i)
if baseline.shape[0] != meta["output_len"]:
raise RuntimeError(f"model output length {baseline.shape[0]} != registry {meta['output_len']}")
if not np.isfinite(baseline).all():
raise RuntimeError("compiled policy produced non-finite outputs")
bundle = { bundle = {
"run_model": jit, "format": POLICY_FORMAT,
"run_policy": jit,
"model_key": meta["key"], "model_key": meta["key"],
"model_sha256": meta["sha256"], "model_sha256": meta["sha256"],
"output_len": int(meta["output_len"]), "output_len": int(meta["output_len"]),
"frame_skip": int(meta["frame_skip"]), "frame_skip": frame_skip,
"input_spec": {name: (tuple(shape), dtype) for name, (shape, dtype) in INPUT_SPEC.items()}, "input_spec": input_spec,
"input_device": Device.DEFAULT, "input_device": device,
} }
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")
with open(tmp, "wb") as f: with open(tmp, "wb") as f:
pickle.dump(bundle, f, protocol=pickle.HIGHEST_PROTOCOL) pickle.dump(bundle, f, protocol=pickle.HIGHEST_PROTOCOL)
del bundle, jit, queues, packed
gc.collect()
print("reload + validate")
with open(tmp, "rb") as f:
jit = pickle.load(f)["run_policy"]
queues = make_queues(input_spec, frame_skip, device)
packed = PackedInputs(input_spec)
outs = []
for i in range(3):
packed.views["traffic_convention"][:] = [1, 0]
packed.views["action_t"][:] = [0.2, 0.3]
out, = jit(warped=_policy_frame(SEED + i, input_spec), packed_npy_inputs=packed.tensor, **queues)
flat = out.numpy().reshape(-1)
packed.views["prev_feat"][:] = flat[meta["output_slices"]["hidden_state"]].reshape(packed.views["prev_feat"].shape)
outs.append(flat)
if not np.array_equal(outs[-1], baseline):
raise RuntimeError("policy outputs differ from baseline after pickle round trip")
if np.array_equal(outs[0], outs[-1]):
raise RuntimeError("policy outputs insensitive to inputs after pickle round trip")
if not all(np.isfinite(o).all() for o in outs):
raise RuntimeError("reloaded policy produced non-finite outputs")
from iqpilot.selfdrive.iqmodeld.parser import PhaseParser
from iqpilot.selfdrive.iqmodeld.tools.compile_supercombo import _slice_outputs, _validate_pose_outputs
_validate_pose_outputs(PhaseParser().parse_vision_outputs(_slice_outputs(outs[-1], meta["output_slices"])))
os.replace(tmp, out_path) os.replace(tmp, out_path)
return out_path return out_path
@@ -118,6 +230,10 @@ def main() -> None:
p.add_argument("--model", default=None, help=f"registry key, one of {sorted(EGPU_MODELS)}") p.add_argument("--model", default=None, help=f"registry key, one of {sorted(EGPU_MODELS)}")
p.add_argument("--onnx", default=None) p.add_argument("--onnx", default=None)
p.add_argument("--output", default=None) p.add_argument("--output", default=None)
p.add_argument("--progress-param", default=None)
p.add_argument("--progress-base", type=float, default=None)
p.add_argument("--progress-span", type=float, default=0.0)
p.add_argument("--format", type=int, default=2, choices=(1, 2))
args = p.parse_args() args = p.parse_args()
if args.model is not None: if args.model is not None:
@@ -136,7 +252,23 @@ def main() -> None:
if onnx_path is None or not os.path.isfile(onnx_path): if onnx_path is None or not os.path.isfile(onnx_path):
raise SystemExit(f"onnx not found for {meta['key']}; pass --onnx or let iqegpumodeld download it first") raise SystemExit(f"onnx not found for {meta['key']}; pass --onnx or let iqegpumodeld download it first")
out = compile_model(meta, onnx_path, args.output or egpu_pkl_path(meta)) stop = None
sampler = None
if args.progress_param and args.progress_base is not None:
import threading
stop = threading.Event()
sampler = threading.Thread(target=_progress_sampler,
args=(args.progress_param, args.progress_base, args.progress_span, stop),
daemon=True)
sampler.start()
try:
build = compile_policy_model if args.format == 2 else compile_model
out = build(meta, onnx_path, args.output or egpu_pkl_path(meta))
finally:
if stop is not None:
stop.set()
if sampler is not None:
sampler.join(timeout=2)
print(f"saved eGPU jit to {out} ({os.path.getsize(out) / 1e6:.2f} MB)") print(f"saved eGPU jit to {out} ({os.path.getsize(out) / 1e6:.2f} MB)")

View File

@@ -104,6 +104,16 @@ class MiciHomeLayout(Widget):
self._iqstandard_txt = gui_app.texture("icons_mici/iqstandard_mode_mici.png", 48, 48) self._iqstandard_txt = gui_app.texture("icons_mici/iqstandard_mode_mici.png", 48, 48)
self._mode_txt = None self._mode_txt = None
self._mic_txt = gui_app.texture("icons_mici/microphone.png", 32, 46) self._mic_txt = gui_app.texture("icons_mici/microphone.png", 32, 46)
self._egpu_txt = gui_app.texture("icons_mici/egpu.png", 62, 46)
self._egpu_green_txt = gui_app.texture("icons_mici/egpu_green.png", 62, 46)
self._egpu_orange_txt = gui_app.texture("icons_mici/egpu_orange.png", 78, 46)
self._mac_txt = gui_app.texture("icons_mici/mac.png", 62, 46)
self._mac_green_txt = gui_app.texture("icons_mici/mac_green.png", 62, 46)
self._mac_orange_txt = gui_app.texture("icons_mici/mac_orange.png", 78, 46)
self._egpu_state: str | None = None
self._mac_state: str | None = None
self._egpu_progress = 0.0
self._mac_progress = 0.0
self._net_type = NETWORK_TYPES.get(NetworkType.none) self._net_type = NETWORK_TYPES.get(NetworkType.none)
self._net_strength = 0 self._net_strength = 0
@@ -176,6 +186,37 @@ class MiciHomeLayout(Widget):
self._version_text = self._get_version_text() self._version_text = self._get_version_text()
self._last_refresh = rl.get_time() self._last_refresh = rl.get_time()
self._update_params() self._update_params()
self._update_dock_status()
def _update_dock_status(self):
p = ui_state.params
egpu_present = bool(getattr(ui_state.sm['deviceState'], "egpuDockPresent", False))
if not egpu_present:
self._egpu_state = None
elif any(p.get_bool(k) for k in ("Offroad_EgpuPcieUnavailable", "Offroad_EgpuOverheated",
"Offroad_EgpuFansObstructed", "Offroad_EgpuUpdateFailed",
"Offroad_EgpuNotDetected")):
self._egpu_state = "orange"
elif p.get_bool("UsbGpuLoading") and not p.get_bool("UsbGpuCompiled"):
self._egpu_state = "compiling"
try:
self._egpu_progress = max(0.0, min(1.0, float(p.get("UsbGpuSetupProgress") or 0.0)))
except (TypeError, ValueError):
self._egpu_progress = 0.0
elif p.get_bool("Offroad_EgpuUsbSlow") or p.get_bool("Offroad_EgpuUncompiled"):
self._egpu_state = "grey"
else:
self._egpu_state = "green"
mac_present = p.get_bool("MacModelPresent") or p.get_bool("MacModelReachable")
if not mac_present:
self._mac_state = None
elif p.get_bool("MacModelFault"):
self._mac_state = "orange"
elif p.get_bool("MacModelReady") or p.get_bool("MacModelActive"):
self._mac_state = "green"
else:
self._mac_state = "grey"
def _update_network_status(self, device_state): def _update_network_status(self, device_state):
self._net_type = device_state.networkType self._net_type = device_state.networkType
@@ -330,6 +371,33 @@ class MiciHomeLayout(Widget):
int(self._rect.y + self.rect.height - self._mic_txt.height / 2 - Y_CENTER), rl.Color(255, 255, 255, 255)) int(self._rect.y + self.rect.height - self._mic_txt.height / 2 - Y_CENTER), rl.Color(255, 255, 255, 255))
last_x += self._mic_txt.width + ITEM_SPACING last_x += self._mic_txt.width + ITEM_SPACING
for state, base, green, orange, progress in (
(self._egpu_state, self._egpu_txt, self._egpu_green_txt, self._egpu_orange_txt, self._egpu_progress),
(self._mac_state, self._mac_txt, self._mac_green_txt, self._mac_orange_txt, self._mac_progress)):
if state is None:
continue
y_top = int(self._rect.y + self.rect.height - base.height / 2 - Y_CENTER)
if state == "compiling":
self._draw_compile_gauge(int(last_x), y_top, base, green, progress)
last_x += base.width + ITEM_SPACING
continue
if state == "green":
tex, tint = green, rl.Color(255, 255, 255, 255)
elif state == "orange":
tex, tint = orange, rl.Color(255, 255, 255, 255)
else:
tex, tint = base, rl.Color(165, 165, 170, 235)
rl.draw_texture(tex, int(last_x), y_top, tint)
last_x += tex.width + ITEM_SPACING
def _draw_compile_gauge(self, x: int, y_top: int, base, fill, progress: float):
rl.draw_texture(base, x, y_top, rl.Color(165, 165, 170, 235))
fill_h = int(base.height * max(0.0, min(1.0, progress)))
if fill_h > 0:
rl.begin_scissor_mode(x, y_top + base.height - fill_h, base.width, fill_h)
rl.draw_texture(fill, x, y_top, rl.Color(255, 255, 255, 255))
rl.end_scissor_mode()
def _draw_cellular_cluster(self, start_x: float, spacing: int, y_center: int, connected: bool) -> float: def _draw_cellular_cluster(self, start_x: float, spacing: int, y_center: int, connected: bool) -> float:
draw_net_txt = {0: self._cell_none_txt, draw_net_txt = {0: self._cell_none_txt,
2: self._cell_low_txt, 2: self._cell_low_txt,

View File

@@ -4,6 +4,7 @@ Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed
import os import os
import re import re
import threading
import time import time
import pyray as rl import pyray as rl
@@ -38,6 +39,31 @@ def _display_model_name(bundle) -> str:
return bundle.internalName if getattr(bundle, "internalName", "") else bundle.displayName return bundle.internalName if getattr(bundle, "internalName", "") else bundle.displayName
def _big_options() -> list[tuple[str, str]]:
try:
from iqpilot.selfdrive.iqmodeld.emac_model_meta import big_models
return big_models(ui_state.params)
except Exception:
return []
def _big_label(key: str) -> str:
for name, display in _big_options():
if name == key:
return display
return key or "lebrowski"
def _refresh_big_catalog() -> None:
def worker():
try:
from iqpilot.selfdrive.iqmodeld.emac_model_meta import refresh_catalog
refresh_catalog(ui_state.params)
except Exception:
pass
threading.Thread(target=worker, daemon=True).start()
class _ModelSelectPanel(NavScroller): class _ModelSelectPanel(NavScroller):
"""A throwaway scroller panel (folder list or bundle list) pushed onto the nav stack.""" """A throwaway scroller panel (folder list or bundle list) pushed onto the nav stack."""
def __init__(self, items): def __init__(self, items):
@@ -99,6 +125,9 @@ class ModelsLayoutMici(NavScroller):
self._current = BigButton(tr("active model")) self._current = BigButton(tr("active model"))
self._current.set_click_callback(self._show_folders) self._current.set_click_callback(self._show_folders)
self._big = BigButton(tr("big model"))
self._big.set_click_callback(self._show_big_models)
self._cancel = BigButton(tr("stop download")) self._cancel = BigButton(tr("stop download"))
self._cancel.set_click_callback(self._cancel_model_request) self._cancel.set_click_callback(self._cancel_model_request)
self._cancel.set_visible(self._is_downloading) self._cancel.set_visible(self._is_downloading)
@@ -129,7 +158,7 @@ class ModelsLayoutMici(NavScroller):
self._lane_speed = MappedParamToggle(tr("lane turn speed"), "IQLaneTurnValue", [tr("slow"), tr("normal"), tr("fast")], _LANE_TURN_VALUES) self._lane_speed = MappedParamToggle(tr("lane turn speed"), "IQLaneTurnValue", [tr("slow"), tr("normal"), tr("fast")], _LANE_TURN_VALUES)
self._lane_speed.set_visible(lambda: self._lane_turn._checked) self._lane_speed.set_visible(lambda: self._lane_turn._checked)
self._main_items = [self._current, self._cancel, self._supercombo, self._vision, self._policy, self._redownload, self._refresh, self._clear, self._main_items = [self._current, self._big, self._cancel, self._supercombo, self._vision, self._policy, self._redownload, self._refresh, self._clear,
self._steer_delay, self._sw_delay, self._lane_turn, self._lane_speed] self._steer_delay, self._sw_delay, self._lane_turn, self._lane_speed]
self._scroller.add_widgets(self._main_items) self._scroller.add_widgets(self._main_items)
@@ -326,6 +355,55 @@ class ModelsLayoutMici(NavScroller):
btns = [_ModelButton(b, self._select_model, self._toggle_favorite, b.ref in favorites) for b in bundles] btns = [_ModelButton(b, self._select_model, self._toggle_favorite, b.ref in favorites) for b in bundles]
gui_app.push_widget(_ModelSelectPanel(btns)) gui_app.push_widget(_ModelSelectPanel(btns))
def _show_big_models(self):
options = _big_options()
if len(options) <= 1:
_refresh_big_catalog()
off = BigButton(tr("Off"))
off.set_click_callback(lambda: self._select_big(None))
btns = [off]
for key, display in options:
btn = BigButton(display)
btn.set_click_callback(lambda k=key: self._select_big(k))
btns.append(btn)
gui_app.push_widget(_ModelSelectPanel(btns))
def _select_big(self, key):
if key is None:
ui_state.params.put_bool("IQEmacEnabled", False)
else:
ui_state.params.put("IQEmacModel", key)
ui_state.params.put_bool("IQEmacEnabled", True)
gui_app.pop_widgets_to(self)
def _big_setup_progress(self) -> float | None:
p = ui_state.params
if p.get_bool("IQEmacEnabled"):
raw = p.get("MacModelDownloadProgress")
loading = not p.get_bool("MacModelReady")
else:
raw = p.get("UsbGpuSetupProgress")
loading = p.get_bool("UsbGpuLoading") and not p.get_bool("UsbGpuCompiled")
if not loading:
return None
try:
return max(0.0, min(1.0, float(raw)))
except (TypeError, ValueError):
return None
def _big_model_value(self) -> str:
p = ui_state.params
dock = bool(getattr(ui_state.sm["deviceState"], "egpuDockPresent", False))
if not p.get_bool("IQEmacEnabled") and not dock:
return tr("Off")
key = p.get("IQEmacModel")
key = key.decode() if isinstance(key, bytes) else (key or "")
label = _big_label(key)
progress = self._big_setup_progress()
if progress is not None and progress < 1.0:
return f"{label} {int(progress * 100)}%"
return label
def _generation_changed(self, bundle) -> bool: def _generation_changed(self, bundle) -> bool:
try: try:
active = self.model_manager.activeBundle active = self.model_manager.activeBundle
@@ -360,6 +438,7 @@ class ModelsLayoutMici(NavScroller):
self._handle_bundle_download_progress() self._handle_bundle_download_progress()
self._current.set_value(self._current_model_value()) self._current.set_value(self._current_model_value())
self._current.set_enabled(ui_state.is_offroad()) self._current.set_enabled(ui_state.is_offroad())
self._big.set_value(self._big_model_value())
target = self._redownload_target_bundle() target = self._redownload_target_bundle()
self._redownload.set_value(_display_model_name(target) if target else "") self._redownload.set_value(_display_model_name(target) if target else "")

View File

@@ -427,6 +427,8 @@ def hardware_thread(end_event, hw_queue) -> None:
# Set ignition based on any panda connected # Set ignition based on any panda connected
onroad_conditions["ignition"] = any(ps.ignitionLine or ps.ignitionCan for ps in pandaStates if ps.pandaType != log.PandaState.PandaType.unknown) onroad_conditions["ignition"] = any(ps.ignitionLine or ps.ignitionCan for ps in pandaStates if ps.pandaType != log.PandaState.PandaType.unknown)
if params.get_bool("IQBenchIgnition"):
onroad_conditions["ignition"] = True
pandaState = pandaStates[0] pandaState = pandaStates[0]
@@ -498,7 +500,7 @@ def hardware_thread(end_event, hw_queue) -> None:
egpu_valid = sm.alive["egpuDockState"] and sm.valid["egpuDockState"] egpu_valid = sm.alive["egpuDockState"] and sm.valid["egpuDockState"]
egpu_dock_status.update(started_ts is None, last_hw_state.usb_state, egpu_dock_flasher.failed, egpu_dock_status.update(started_ts is None, last_hw_state.usb_state, egpu_dock_flasher.failed,
params.get_bool("UsbGpuLoading"), params.get("UsbGpuActive"), params.get_bool("UsbGpuLoading"), params.get("UsbGpuActive"),
params.get_bool("UsbGpuCompiled"), params.get_bool("UsbGpuReady"),
sm["egpuDockState"] if egpu_valid else None, set_offroad_alert_if_changed) sm["egpuDockState"] if egpu_valid else None, set_offroad_alert_if_changed)
msg.deviceState.screenBrightnessPercent = HARDWARE.get_screen_brightness() msg.deviceState.screenBrightnessPercent = HARDWARE.get_screen_brightness()

View File

@@ -207,10 +207,20 @@ def _patch_mock_state():
activeBundle = _MockBundle() activeBundle = _MockBundle()
selectedBundle = None # None = not downloading selectedBundle = None # None = not downloading
from iqpilot.cereal import log as _log
class _MockDeviceState:
networkType = _log.DeviceState.NetworkType.wifi
networkStrength = type("NS", (), {"raw": 3})()
egpuDockPresent = True
started = False
freeSpacePercent = 50.0
memoryUsagePercent = 40
class _MockSM: class _MockSM:
"""Minimal SubMaster-like dict that returns sensible defaults.""" """Minimal SubMaster-like dict that returns sensible defaults."""
_data = { _data = {
"iqModelManager": _MockModelManager(), "iqModelManager": _MockModelManager(),
"deviceState": _MockDeviceState(),
} }
def __getitem__(self, key): def __getitem__(self, key):
return self._data.get(key, type("Empty", (), {"enabled": False})()) return self._data.get(key, type("Empty", (), {"enabled": False})())
@@ -330,6 +340,16 @@ def load_panel(name: str):
widget = cls() widget = cls()
widget.set_rect(rect) widget.set_rect(rect)
widget.show_event() widget.show_event()
eg = os.environ.get("IQ_EGPU_STATE")
mc = os.environ.get("IQ_MAC_STATE")
if (eg or mc) and hasattr(widget, "_egpu_state"):
widget._egpu_state = eg or None
widget._mac_state = mc or None
widget._egpu_progress = float(os.environ.get("IQ_EGPU_PROGRESS", "0") or 0)
widget._mac_progress = float(os.environ.get("IQ_MAC_PROGRESS", "0") or 0)
widget._update_dock_status = lambda: None
return widget return widget