diff --git a/artifacts/iqpilot_emac_private/manifest.json b/artifacts/iqpilot_emac_private/manifest.json index 3cf308598..6bdc7e3a2 100644 --- a/artifacts/iqpilot_emac_private/manifest.json +++ b/artifacts/iqpilot_emac_private/manifest.json @@ -1,8 +1,8 @@ { "python/_iqclosure/_vendored.json": { "mode": 420, - "sha256": "c5dde802e17116d9759e661e7d80a1a86d396fb3040734c1eadb2a50c5a3cf4c", - "size": 3439 + "sha256": "2cd59a784bfe6c6d1ce382e832dd355f62900b1f73a74ed91c53397aa8581754", + "size": 3495 }, "python/_iqclosure/iqpilot/__init__.py": { "mode": 420, @@ -196,8 +196,8 @@ }, "python/_iqclosure/iqpilot/selfdrive/iqmodeld/egpu_helpers.py": { "mode": 420, - "sha256": "5d00df0cf831ea75a91d1d817818dc3847d971e581170671f12e289461eccabc", - "size": 4846 + "sha256": "4df0ac0a3f720ee01a70f82908e582682e84848023592a72bd45f2ca74c9b161", + "size": 5535 }, "python/_iqclosure/iqpilot/selfdrive/iqmodeld/egpu_model.py": { "mode": 420, @@ -219,6 +219,11 @@ "sha256": "dd1559099407687ee2dc81e180e05b9954e712c39e1a82e39c1b00b70c43374a", "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": { "mode": 420, "sha256": "2d706993df418501d93dc766ffe452131e565bfbc2e8412b527dc14915e92832", @@ -526,22 +531,22 @@ }, "python/iqpilot_private/emac/bulk_transport.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "0b580306ea908ad46b280f2fd5f828083e736b83c951a33ab007a6fb8a642442", - "size": 202152 + "sha256": "3f55e4c621ea86c0a6ac9ecac091fa90491048df5fc5bf09cbb668f0c0b71051", + "size": 334016 }, "python/iqpilot_private/emac/mac_client.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "b4d721d043cad44826a7b1ac304105ff65676a00570e1b1e0f845c2a4ab88d51", - "size": 267376 + "sha256": "e748eba2eec567e5f0c9e1dce60ef71795d2a99f58238ad89495dcee7672c70c", + "size": 267952 }, "python/iqpilot_private/emac/mac_protocol.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "c63d56f9a4329f8a16c4a2144ddc7761876fd20f7c51ac40005c516573f7b19e", + "sha256": "46f79e0fe9d799003b49e5fe11969a1716974a9c6d20d52488d79ef8b7137a5a", "size": 203544 }, "python/iqpilot_private/emac/maciqmodeld.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "311a13e5db0e6f8719e2b6383a2d9c74c2f70db28bdd424c5ceec4958d70f201", + "sha256": "b7acc1edcf50dd2e9c2d88f2f4824d0a28b0def3a57b144a340ddca1bdd14254", "size": 333336 }, "runtime": { @@ -553,9 +558,9 @@ } }, "signatures": { - "python/iqpilot_private/emac/bulk_transport.cpython-312-aarch64-linux-gnu.so": "mxq4MaNi+2F0fpbVaypztLeUf9DqFo8LSohXAzb+1tXsdKZZYQmmU90hlqIRFv4PuwWRyVbXJE/Ge48amXDhBw==", - "python/iqpilot_private/emac/mac_client.cpython-312-aarch64-linux-gnu.so": "Zm41VmfRMMJkj3TvxrqLLyUy+0Xq71z9V18gpKkstM5hWtrZsdP4Q+G04eeiujl45Q8hO1ysW0MMP6gJFQGfAg==", - "python/iqpilot_private/emac/mac_protocol.cpython-312-aarch64-linux-gnu.so": "3quWJnVdRjcA+zrHojJIRcmxySkJAbskBYfVpXjpN3BHxFvjrC+TK9ReVEeTwQdp6b6xR0Fk+40Ngo0MphsbAQ==", - "python/iqpilot_private/emac/maciqmodeld.cpython-312-aarch64-linux-gnu.so": "wqTQKJ8HOYoBP6dLbdVLvMRZ81kMfvwRB77qSj3jikgyPtz2ARen/Eo/SydGqQByqn0FUR7mHWm65I7/flEEBw==" + "python/iqpilot_private/emac/bulk_transport.cpython-312-aarch64-linux-gnu.so": "zdchbAhGhL0eeX6qgQUw0CwrPGIg896Sv/0AG0hHrJ/1Ek7KnB4llxmWUA/e4HB4hlCqTEMO416N3csIfDikBA==", + "python/iqpilot_private/emac/mac_client.cpython-312-aarch64-linux-gnu.so": "p0ukaERkDTTF6vVVn0TK1ImrPMArUWmqAY2kJnctlb38486SIeFZxC4gCP0cU9++j7i7ydmXNOcUqxG7qIPECQ==", + "python/iqpilot_private/emac/mac_protocol.cpython-312-aarch64-linux-gnu.so": "5rLpvBoiOP+/N/zblRnXcltpOtULO3uZx2bQYeCM/A8x8m9HLhIBUMJzSVIYj5VcbvgHmzc911UE18RrwEmyCg==", + "python/iqpilot_private/emac/maciqmodeld.cpython-312-aarch64-linux-gnu.so": "otKpewDX8CPqirHPXn+mTht7oi/AgkQiZ2VXPZM3/wryLSvzRKJzMyf5nXZ/lb0Z8J8vsFKUkdVhxKgYkeLIDw==" } } diff --git a/artifacts/iqpilot_emac_private/manifest.json.sig b/artifacts/iqpilot_emac_private/manifest.json.sig index bae964284..01fe5bbaa 100644 --- a/artifacts/iqpilot_emac_private/manifest.json.sig +++ b/artifacts/iqpilot_emac_private/manifest.json.sig @@ -1 +1 @@ -ML9DoBJeI+4hKgeyK5ssDKYFVeaAg1dVrqTrEjizxFHJtuAv1wYohJz8hxdJnvkQgvrA4pFVsOsu3kvEdIXWAg== +9uZxIFB8JGkbVFGkwQJ+7gxvqL+EJf7PS0DqSsFYuiaw39lYS4qp5rzPOmn0ixSDSYlzsEwnwq5ojMdbX/ZJCQ== diff --git a/artifacts/iqpilot_emac_private/python/_iqclosure/_vendored.json b/artifacts/iqpilot_emac_private/python/_iqclosure/_vendored.json index 9fcfa0148..7519942ae 100644 --- a/artifacts/iqpilot_emac_private/python/_iqclosure/_vendored.json +++ b/artifacts/iqpilot_emac_private/python/_iqclosure/_vendored.json @@ -34,6 +34,7 @@ "iqpilot.selfdrive.iqmodeld.emac_model_meta", "iqpilot.selfdrive.iqmodeld.messaging", "iqpilot.selfdrive.iqmodeld.metadata", + "iqpilot.selfdrive.iqmodeld.model_bundle_downloader", "iqpilot.selfdrive.iqmodeld.model_channel", "iqpilot.selfdrive.iqmodeld.model_warp", "iqpilot.selfdrive.iqmodeld.models", diff --git a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/egpu_helpers.py b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/egpu_helpers.py index d09895c86..9e953fb9a 100644 --- a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/egpu_helpers.py +++ b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/egpu_helpers.py @@ -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") +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: from iqpilot.system.hardware.hw import Paths 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 +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: import pathlib diff --git a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/model_bundle_downloader.py b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/model_bundle_downloader.py new file mode 100644 index 000000000..35ac6acaf --- /dev/null +++ b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/iqmodeld/model_bundle_downloader.py @@ -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}") diff --git a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/bulk_transport.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/bulk_transport.cpython-312-aarch64-linux-gnu.so index 5f5fc962e..3e00ace4f 100755 Binary files a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/bulk_transport.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/bulk_transport.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/mac_client.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/mac_client.cpython-312-aarch64-linux-gnu.so index 2c3698a90..68453dfd0 100755 Binary files a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/mac_client.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/mac_client.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/mac_protocol.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/mac_protocol.cpython-312-aarch64-linux-gnu.so index 90bb87e16..62c03d406 100755 Binary files a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/mac_protocol.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/mac_protocol.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/maciqmodeld.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/maciqmodeld.cpython-312-aarch64-linux-gnu.so index 1b142e069..6b8089334 100755 Binary files a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/maciqmodeld.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/maciqmodeld.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_model_selector_private/manifest.json b/artifacts/iqpilot_model_selector_private/manifest.json index 94dc9c020..4c6203a9b 100644 --- a/artifacts/iqpilot_model_selector_private/manifest.json +++ b/artifacts/iqpilot_model_selector_private/manifest.json @@ -176,7 +176,7 @@ }, "python/iqpilot_private/models/__init__.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "b7ae194bc219d344d92e77e7ad56232c48e75bab4d0bdcb2ebf20dccc7ba2c4d", + "sha256": "c6ff7efa8f20decbd9ee75a2b11d6be6b2b0888b45d90dce65ac3a1615e3afc5", "size": 67616 }, "python/iqpilot_private/models/__init__.py": { @@ -186,37 +186,37 @@ }, "python/iqpilot_private/models/big_catalog.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "447634f6ee186df629e3fc03556fab3222d84037da0e9061aa44e7238b0f9b5e", + "sha256": "60bb1b539739e75249c3034ff505aeb9bf38d6fcf8d7f713d61395104bee0002", "size": 133840 }, "python/iqpilot_private/models/egpu_model.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "5a69a9967bdeaf8258b26487be40420c8f37935161699527f2bafc3ddfa5e710", + "sha256": "7111179a66be1ac8bbcefcd2c6f836150e9e31b01d36244315203123f980beb8", "size": 68112 }, "python/iqpilot_private/models/emac_model_meta.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "24785c0b8eee854e1da2f03ee961de6650fcd7b975797c45d5d1d8ea995b8273", + "sha256": "16bec7e29dc9b07b743d523735eb3adfd5b43b0ffa7cc89d189ee050065aa08d", "size": 68264 }, "python/iqpilot_private/models/fetcher.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "37042bfe3e87db35e8abb8a03562c90be5ce835e832b2b4159f57e6f2643764e", + "sha256": "8eaf6bc52ad788587e3a9bfbd06246f68931c8a151772e37c895c396001e5243", "size": 136200 }, "python/iqpilot_private/models/git_auth.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "9818f5eb5c850cdee9ec6d62d239de0451659cd62b891c01083433f41ca8dbde", + "sha256": "c785e13bf86fcfdc5bf0d5e5acc6a49b764f800e4a5e87bb740cd187fdeb4584", "size": 67864 }, "python/iqpilot_private/models/helpers.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "6e6c9e9e66579d1f03861f1535da3a960f53f50e734a6030b41b9d8f8516c981", + "sha256": "d98244f8f9b3b1e1f412802d93095a0a05dedf5c6a98c9cefbee6c27e999d341", "size": 137944 }, "python/iqpilot_private/models/manager.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "ca16d91129cf70227a92b81dd4204760681fc6f2ca7bd9ed84e58b7cd2efd2de", + "sha256": "9df118ff8a45497e258ac4cf520db3c80f74b8cd22a41b54bba26d372bc6a8e5", "size": 340000 }, "runtime": { @@ -228,13 +228,13 @@ } }, "signatures": { - "python/iqpilot_private/models/__init__.cpython-312-aarch64-linux-gnu.so": "lSYzYS2zoycLPzqpLu8RE5hnpVFgSA0e6ZXgH57Ve2f3jwV9OSCbM+JHqxy5fDDiPW1ksRM6j01vFE+6SY5hCw==", - "python/iqpilot_private/models/big_catalog.cpython-312-aarch64-linux-gnu.so": "WHHAjMetD6q1tsUdG54ulOLADQImspgfqr7p7lqPp0NMmvy+MYVJLQPmxrjrSokZTwtGivlLNEy8wo4EZ6OuCQ==", - "python/iqpilot_private/models/egpu_model.cpython-312-aarch64-linux-gnu.so": "+QTJvOd9zd1UfXcwn0+EhoMHhGMvPyBOB59GglVi5fx9j1TUqDWu00Ip/30IlbxWTbTMZQzDNFWR3puQZ/tgCA==", - "python/iqpilot_private/models/emac_model_meta.cpython-312-aarch64-linux-gnu.so": "e9ZmfuFD/FSWPAt/9nmyFafkGojmCuRxmaHixSCMPLaFVDrwwomuDoaq4ogaQ6HmHDdCosBkNh7w/6ZIHgpzDg==", - "python/iqpilot_private/models/fetcher.cpython-312-aarch64-linux-gnu.so": "ImIktR4cuqUzWtoZe1nSA0sIygmpuVW76jNZwerd+qG1qmJojkW7mfQzZ6yiwRTV51FybjLjLzhZgAA5kqY9Cw==", - "python/iqpilot_private/models/git_auth.cpython-312-aarch64-linux-gnu.so": "qGUpnjJFC67qdIuc3JeTtDHVKRuK9oEe59f/43IJF3avDTzbFTHnLWM3AfvO+j3i/m4vuuRjAlKVCQEFRlNWDA==", - "python/iqpilot_private/models/helpers.cpython-312-aarch64-linux-gnu.so": "rB3MDS2Ue0p8889DbKn+gpzyaM1IeR4cb38uy3taitPwV4Em2Zbyr47W2hbvlKL32m2pBWwiJCM8EeqlafEtDg==", - "python/iqpilot_private/models/manager.cpython-312-aarch64-linux-gnu.so": "qsLTZana4Y5dxS0m8gGvtYVTFeXixkPUawT2SGwQF8ccxRQogAUt1bQJfJLZA4xmOnTG2Da0qhxFQ13VsFBeCA==" + "python/iqpilot_private/models/__init__.cpython-312-aarch64-linux-gnu.so": "XBVN/BJBOnlVrPJ0pq/+rAkGwSDkJk3t29YpPaYsWCQPWD0JADTip4a8vj0GvCQgXdGwNsMJb2b7Cu6TeoOOAw==", + "python/iqpilot_private/models/big_catalog.cpython-312-aarch64-linux-gnu.so": "zhE70qcpwK9vSvqYp7fKuMptsV2JMMpmsnUeyFuA89mtFDKzUoJi+jxwOsvcH1b0qk6CuemH86l0/UpA0AbRAg==", + "python/iqpilot_private/models/egpu_model.cpython-312-aarch64-linux-gnu.so": "4JFNjPPV2i8xPXrWrwpkuOWPDRCD/m0+bVGgWO9TEmFLKbAu+RQslAHesptxfDAyomJbeRLBvXk1KVRaTzQ+CA==", + "python/iqpilot_private/models/emac_model_meta.cpython-312-aarch64-linux-gnu.so": "Q1luLZ5apcCJ3Im8Py3uP0D42VYXdsw5y48FAtofFrCQJlZ+LTPshjSVbu2GYQnxny9fFt4J6DT9zvMtLheqAA==", + "python/iqpilot_private/models/fetcher.cpython-312-aarch64-linux-gnu.so": "YPc7Ix5gW6YCQX2DrpL5bWu5RKMIOCJvaR/xDwTJmo3/dfkgvfAv7jG41rTxpXaRl8rM8ipUoPI8tlKGzucqDw==", + "python/iqpilot_private/models/git_auth.cpython-312-aarch64-linux-gnu.so": "R6WdFIgsLn4/8fq5qSU64ye7n+Ql9tr3Vhy4gYKIGKWY5GkXplkc8KMA1ciO7yy0n6Qw5HPCmGKrAbkQerrfBw==", + "python/iqpilot_private/models/helpers.cpython-312-aarch64-linux-gnu.so": "BoA4zydMAnpLyAFxzjwvggxB8Hzx+OJd3puAAckzi0UiiGhdiTGl9jexUcGpOolST3pPCRSoCXT0fVihW/OFDw==", + "python/iqpilot_private/models/manager.cpython-312-aarch64-linux-gnu.so": "8VccT47QBNee1ZcnKSsHchV10dOJP0q55FtabpSZr+azLP+hmWiClG3j9VqRe5ctAzuMa53m7eSRjXiTKCpwDw==" } } diff --git a/artifacts/iqpilot_model_selector_private/manifest.json.sig b/artifacts/iqpilot_model_selector_private/manifest.json.sig index 72a9da140..ca30f6a04 100644 --- a/artifacts/iqpilot_model_selector_private/manifest.json.sig +++ b/artifacts/iqpilot_model_selector_private/manifest.json.sig @@ -1 +1 @@ -ZZZzIcyCSoU2lVTy3IXBcSS//qvFxL5C0ivJT9huKojaRWV7cBqJDBRD457NwknzAaUHiz9HmAwguFLjvWgxCA== +LQYoKAbNbtkF/8GkU70iyR+NZQI0fmM7PWUQ2IphOB0P4nCRjgRCnkkE/g3KS+d4f/R60fgakG135GVCQgsaBw== diff --git a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/__init__.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/__init__.cpython-312-aarch64-linux-gnu.so index 189743499..bb552bd23 100755 Binary files a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/__init__.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/__init__.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/big_catalog.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/big_catalog.cpython-312-aarch64-linux-gnu.so index cd3fa6b43..a2c7fe82e 100755 Binary files a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/big_catalog.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/big_catalog.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/egpu_model.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/egpu_model.cpython-312-aarch64-linux-gnu.so index ee7176c44..c4328a9b9 100755 Binary files a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/egpu_model.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/egpu_model.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/emac_model_meta.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/emac_model_meta.cpython-312-aarch64-linux-gnu.so index 422e0d5a1..2f8f1719c 100755 Binary files a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/emac_model_meta.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/emac_model_meta.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/fetcher.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/fetcher.cpython-312-aarch64-linux-gnu.so index 90de4ba7a..0ef104463 100755 Binary files a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/fetcher.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/fetcher.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/git_auth.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/git_auth.cpython-312-aarch64-linux-gnu.so index 622d8bb7c..c9a9fdc05 100755 Binary files a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/git_auth.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/git_auth.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/helpers.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/helpers.cpython-312-aarch64-linux-gnu.so index 89887192c..7af37b0b7 100755 Binary files a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/helpers.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/helpers.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/manager.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/manager.cpython-312-aarch64-linux-gnu.so index a7bf20fae..0926516c7 100755 Binary files a/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/manager.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_model_selector_private/python/iqpilot_private/models/manager.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_navd_private/.source_sha b/artifacts/iqpilot_navd_private/.source_sha new file mode 100644 index 000000000..c835b7dab --- /dev/null +++ b/artifacts/iqpilot_navd_private/.source_sha @@ -0,0 +1 @@ +44200007e6d1ccd1e6016db08abf3f54d733913f diff --git a/iqpilot/cereal/custom.capnp b/iqpilot/cereal/custom.capnp index cc561146a..d056049a2 100644 --- a/iqpilot/cereal/custom.capnp +++ b/iqpilot/cereal/custom.capnp @@ -599,6 +599,8 @@ struct IQNavState @0xaae9afb364368cd9 { cameraChime @59 :Bool; cameraTrusted @60 :Bool; cameraSourceAge @61 :Float32; + mapboxSpeedLimit @62 :Float32; + mapboxSpeedLimitValid @63 :Bool; enum CameraType { none @0; diff --git a/iqpilot/common/params_keys.h b/iqpilot/common/params_keys.h index 8dfab24e2..47cfece36 100644 --- a/iqpilot/common/params_keys.h +++ b/iqpilot/common/params_keys.h @@ -199,6 +199,7 @@ inline static std::unordered_map keys = { {"ModelRunnerTypeCache", {CLEAR_ON_ONROAD_TRANSITION, INT}}, {"ForceOnroadUntil", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, INT, "0"}}, {"IQAlwaysOffroad", {CLEAR_ON_MANAGER_START, BOOL}}, + {"IQBenchIgnition", {PERSISTENT, BOOL, "0"}}, {"Offroad_TiciSupport", {CLEAR_ON_MANAGER_START, JSON}}, {"OnroadScreenOffBrightness", {PERSISTENT, INT, "0"}}, {"OnroadScreenOffTimer", {PERSISTENT, INT, "15"}}, @@ -241,6 +242,7 @@ inline static std::unordered_map keys = { {"MacModelReachable", {CLEAR_ON_MANAGER_START, BOOL}}, {"MacModelReady", {CLEAR_ON_MANAGER_START, BOOL}}, {"MacModelActive", {CLEAR_ON_MANAGER_START, BOOL}}, + {"MacModelFault", {CLEAR_ON_MANAGER_START, BOOL}}, {"MacModelFailed", {CLEAR_ON_MANAGER_START, BOOL}}, {"MacModelLastError", {CLEAR_ON_MANAGER_START, STRING}}, {"MacModelLatencyMs", {CLEAR_ON_MANAGER_START, FLOAT, "0.0"}}, @@ -251,6 +253,7 @@ inline static std::unordered_map keys = { {"IQEgpuDisabled", {PERSISTENT, BOOL, "0"}}, {"UsbGpuPresent", {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}}, {"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}}, @@ -319,6 +322,7 @@ inline static std::unordered_map keys = { {"MacModelCompiled", {CLEAR_ON_MANAGER_START, BOOL}}, {"MacModelReady", {CLEAR_ON_MANAGER_START, BOOL}}, {"MacModelActive", {CLEAR_ON_MANAGER_START, BOOL}}, + {"MacModelFault", {CLEAR_ON_MANAGER_START, BOOL}}, {"MacModelFailed", {CLEAR_ON_MANAGER_START, BOOL}}, {"MacModelLastError", {CLEAR_ON_MANAGER_START, STRING}}, {"MacModelDownloadProgress", {CLEAR_ON_MANAGER_START, FLOAT}}, diff --git a/iqpilot/selfdrive/assets/icons_mici/egpu.png b/iqpilot/selfdrive/assets/icons_mici/egpu.png new file mode 100644 index 000000000..6341111ee Binary files /dev/null and b/iqpilot/selfdrive/assets/icons_mici/egpu.png differ diff --git a/iqpilot/selfdrive/assets/icons_mici/egpu_green.png b/iqpilot/selfdrive/assets/icons_mici/egpu_green.png new file mode 100644 index 000000000..4f05cf1e4 Binary files /dev/null and b/iqpilot/selfdrive/assets/icons_mici/egpu_green.png differ diff --git a/iqpilot/selfdrive/assets/icons_mici/egpu_orange.png b/iqpilot/selfdrive/assets/icons_mici/egpu_orange.png new file mode 100644 index 000000000..6a3fd3d00 Binary files /dev/null and b/iqpilot/selfdrive/assets/icons_mici/egpu_orange.png differ diff --git a/iqpilot/selfdrive/assets/icons_mici/mac.png b/iqpilot/selfdrive/assets/icons_mici/mac.png new file mode 100644 index 000000000..e33feb574 Binary files /dev/null and b/iqpilot/selfdrive/assets/icons_mici/mac.png differ diff --git a/iqpilot/selfdrive/assets/icons_mici/mac_green.png b/iqpilot/selfdrive/assets/icons_mici/mac_green.png new file mode 100644 index 000000000..308b38ca9 Binary files /dev/null and b/iqpilot/selfdrive/assets/icons_mici/mac_green.png differ diff --git a/iqpilot/selfdrive/assets/icons_mici/mac_orange.png b/iqpilot/selfdrive/assets/icons_mici/mac_orange.png new file mode 100644 index 000000000..027306604 Binary files /dev/null and b/iqpilot/selfdrive/assets/icons_mici/mac_orange.png differ diff --git a/iqpilot/selfdrive/controls/lib/speed_limit_controller.py b/iqpilot/selfdrive/controls/lib/speed_limit_controller.py index 3cdf72ecf..7f9251c6b 100644 --- a/iqpilot/selfdrive/controls/lib/speed_limit_controller.py +++ b/iqpilot/selfdrive/controls/lib/speed_limit_controller.py @@ -777,7 +777,15 @@ class SpeedLimitController: self.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 resolved_limit, resolved_source = self._resolver.resolve(dashboard_limit, online_limit, slc_params) diff --git a/iqpilot/selfdrive/controls/lib/tests/test_slc_speed_limit_control.py b/iqpilot/selfdrive/controls/lib/tests/test_slc_speed_limit_control.py index 2e2acf9c4..45fe0eed2 100644 --- a/iqpilot/selfdrive/controls/lib/tests/test_slc_speed_limit_control.py +++ b/iqpilot/selfdrive/controls/lib/tests/test_slc_speed_limit_control.py @@ -663,3 +663,51 @@ def test_construction_zone_fires_event_once_per_zone_entry(): assert event not in controller.pending_events controller.update_limits(0.0, None, True, 33.0, 30.0, _construction_sm(), slc_params) 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) diff --git a/iqpilot/selfdrive/iqmodeld/egpu_helpers.py b/iqpilot/selfdrive/iqmodeld/egpu_helpers.py index d09895c86..9e953fb9a 100644 --- a/iqpilot/selfdrive/iqmodeld/egpu_helpers.py +++ b/iqpilot/selfdrive/iqmodeld/egpu_helpers.py @@ -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") +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: from iqpilot.system.hardware.hw import Paths 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 +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: import pathlib diff --git a/iqpilot/selfdrive/iqmodeld/egpu_pipeline.py b/iqpilot/selfdrive/iqmodeld/egpu_pipeline.py index e0cd2f20b..428b095a2 100644 --- a/iqpilot/selfdrive/iqmodeld/egpu_pipeline.py +++ b/iqpilot/selfdrive/iqmodeld/egpu_pipeline.py @@ -5,6 +5,7 @@ from __future__ import annotations 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 @@ -25,13 +26,17 @@ class EgpuPipeline: def run(self, warped: np.ndarray, desire_vec: np.ndarray, traffic_convention: np.ndarray, action_t: np.ndarray) -> np.ndarray: - 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 isinstance(self.infer_fn, PolicyRunner): + 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: raise EgpuPipelineError(f"eGPU output length {out.shape[0]} != {self.output_len}") if not np.isfinite(out).all(): 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 diff --git a/iqpilot/selfdrive/iqmodeld/egpu_policy.py b/iqpilot/selfdrive/iqmodeld/egpu_policy.py new file mode 100644 index 000000000..726df1e66 --- /dev/null +++ b/iqpilot/selfdrive/iqmodeld/egpu_policy.py @@ -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 diff --git a/iqpilot/selfdrive/iqmodeld/iqegpumodeld.py b/iqpilot/selfdrive/iqmodeld/iqegpumodeld.py index 0a48111c0..c8fadbe4e 100644 --- a/iqpilot/selfdrive/iqmodeld/iqegpumodeld.py +++ b/iqpilot/selfdrive/iqmodeld/iqegpumodeld.py @@ -38,8 +38,8 @@ from iqpilot.selfdrive.iqmodeld.driving_action import ( DESIRE_LEN, LAT_SMOOTH_SECONDS, LONG_SMOOTH_SECONDS, get_action_from_model, ) from iqpilot.selfdrive.iqmodeld.egpu_helpers import ( - download_onnx, egpu_pkl_path, egpu_present_consented, egpu_selected, local_onnx, patch_tinygrad_fetch_fw, - quarantine_artifact, resolve_backend, usbgpu_present, + download_onnx, download_precompiled, egpu_pkl_path, egpu_policy_pkl_path, egpu_present_consented, egpu_selected, local_onnx, + patch_tinygrad_fetch_fw, quarantine_artifact, resolve_backend, usbgpu_present, ) from iqpilot.selfdrive.iqmodeld.egpu_model import resolve_egpu_model 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.metadata import Meta20hz from iqpilot.selfdrive.iqmodeld.model_channel import BIG_CHANNEL, ModelChannel +from iqpilot.selfdrive.iqmodeld.egpu_policy import POLICY_FORMAT, PolicyRunner from iqpilot.selfdrive.iqmodeld.model_warp import FrameWarp 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: 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", - "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, env=compile_env, preexec_fn=lambda: os.nice(20)) 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}") +_precompiled_tried = False + + def _ensure_artifact(params: Params, meta: dict) -> str: - pkl_path = egpu_pkl_path(meta) - if os.path.isfile(pkl_path): - return pkl_path + global _precompiled_tried + policy_path = egpu_policy_pkl_path(meta) + if os.path.isfile(policy_path): + return policy_path + legacy_path = egpu_pkl_path(meta) params.put_bool("UsbGpuCompiled", False) + params.put_bool("UsbGpuReady", False) + + if meta.get("egpu_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) if onnx_path is None: params.put("UsbGpuSetupProgress", "0.0") @@ -114,14 +147,14 @@ def _ensure_artifact(params: Params, meta: dict) -> str: def _prog(p: float) -> None: if p - last[0] >= 0.02 or p >= 1.0: 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) cloudlog.warning(f"iqegpumodeld compiling {meta['key']} for USB-AMD (one-time, can take minutes)") - _compile_in_subprocess(meta, onnx_path, pkl_path) - cloudlog.warning(f"iqegpumodeld compiled -> {pkl_path}") - return pkl_path + _compile_in_subprocess(meta, onnx_path, policy_path) + cloudlog.warning(f"iqegpumodeld compiled -> {policy_path}") + return policy_path 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"]): quarantine_artifact(pkl_path, "pkl output_len mismatch") 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"] input_dev = bundle.get("input_device", "AMD") 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: zeros = {name: np.zeros(shape, dtype=dtype) for name, (shape, dtype) in input_spec.items()} 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 if out.shape[0] != output_len or not np.isfinite(out).all(): 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("UsbGpuCompiled", True) + params.put_bool("UsbGpuReady", True) params.put("UsbGpuSetupProgress", "1.0") cloudlog.warning(f"iqegpumodeld model: {meta['key']} ({meta['model_name']})") cloudlog.warning(f"iqegpumodeld model up (warmup {warm_s * 1e3:.0f}ms)") diff --git a/iqpilot/selfdrive/iqmodeld/model_bundle_downloader.py b/iqpilot/selfdrive/iqmodeld/model_bundle_downloader.py new file mode 100644 index 000000000..35ac6acaf --- /dev/null +++ b/iqpilot/selfdrive/iqmodeld/model_bundle_downloader.py @@ -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}") diff --git a/iqpilot/selfdrive/iqmodeld/tests/test_egpu_policy.py b/iqpilot/selfdrive/iqmodeld/tests/test_egpu_policy.py new file mode 100644 index 000000000..670d9e3c6 --- /dev/null +++ b/iqpilot/selfdrive/iqmodeld/tests/test_egpu_policy.py @@ -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) diff --git a/iqpilot/selfdrive/iqmodeld/tools/compile_egpu_model.py b/iqpilot/selfdrive/iqmodeld/tools/compile_egpu_model.py index 7402a36a9..1d2bc9846 100644 --- a/iqpilot/selfdrive/iqmodeld/tools/compile_egpu_model.py +++ b/iqpilot/selfdrive/iqmodeld/tools/compile_egpu_model.py @@ -4,6 +4,7 @@ Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed from __future__ import annotations import argparse +import gc import os import pickle import time @@ -12,6 +13,7 @@ os.environ.setdefault("DEV", "USB+AMD:LLVM") os.environ.setdefault("FLOAT16", "1") os.environ.setdefault("JIT_BATCH_SIZE", "0") os.environ.setdefault("GMMU", "0") +os.environ.setdefault("TC_OPT", "2") import numpy as np @@ -24,6 +26,22 @@ INPUT_SPEC = dict(MODEL_INPUT_SPEC) patch_tinygrad_fetch_fw() 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: @@ -81,8 +99,27 @@ def compile_model(meta: dict, onnx_path: str, out_path: str) -> str: if not np.isfinite(baseline).all(): raise RuntimeError("compiled model produced non-finite outputs") - print("pickle round trip") - jit = pickle.loads(pickle.dumps(jit)) + bundle = { + "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): raise RuntimeError("outputs differ from baseline after pickle round trip") 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 _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 = { - "run_model": jit, + "format": POLICY_FORMAT, + "run_policy": 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, + "frame_skip": frame_skip, + "input_spec": input_spec, + "input_device": device, } 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, 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) 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("--onnx", 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() 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): 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)") diff --git a/iqpilot/selfdrive/ui/mici/layouts/home.py b/iqpilot/selfdrive/ui/mici/layouts/home.py index 0c986bd77..5eef75427 100644 --- a/iqpilot/selfdrive/ui/mici/layouts/home.py +++ b/iqpilot/selfdrive/ui/mici/layouts/home.py @@ -104,6 +104,16 @@ class MiciHomeLayout(Widget): self._iqstandard_txt = gui_app.texture("icons_mici/iqstandard_mode_mici.png", 48, 48) self._mode_txt = None 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_strength = 0 @@ -176,6 +186,37 @@ class MiciHomeLayout(Widget): self._version_text = self._get_version_text() self._last_refresh = rl.get_time() 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): 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)) 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: draw_net_txt = {0: self._cell_none_txt, 2: self._cell_low_txt, diff --git a/iqpilot/selfdrive/ui/mici/layouts/settings/models.py b/iqpilot/selfdrive/ui/mici/layouts/settings/models.py index 9226f5ae0..9586367df 100644 --- a/iqpilot/selfdrive/ui/mici/layouts/settings/models.py +++ b/iqpilot/selfdrive/ui/mici/layouts/settings/models.py @@ -4,6 +4,7 @@ Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed import os import re +import threading import time import pyray as rl @@ -38,6 +39,31 @@ def _display_model_name(bundle) -> str: 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): """A throwaway scroller panel (folder list or bundle list) pushed onto the nav stack.""" def __init__(self, items): @@ -99,6 +125,9 @@ class ModelsLayoutMici(NavScroller): self._current = BigButton(tr("active model")) 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.set_click_callback(self._cancel_model_request) 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.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._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] 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: try: active = self.model_manager.activeBundle @@ -360,6 +438,7 @@ class ModelsLayoutMici(NavScroller): self._handle_bundle_download_progress() self._current.set_value(self._current_model_value()) self._current.set_enabled(ui_state.is_offroad()) + self._big.set_value(self._big_model_value()) target = self._redownload_target_bundle() self._redownload.set_value(_display_model_name(target) if target else "") diff --git a/iqpilot/system/hardware/hardwared.py b/iqpilot/system/hardware/hardwared.py index c918d704b..10fe93ed2 100755 --- a/iqpilot/system/hardware/hardwared.py +++ b/iqpilot/system/hardware/hardwared.py @@ -427,6 +427,8 @@ def hardware_thread(end_event, hw_queue) -> None: # 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) + if params.get_bool("IQBenchIgnition"): + onroad_conditions["ignition"] = True 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_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("UsbGpuCompiled"), + params.get_bool("UsbGpuReady"), sm["egpuDockState"] if egpu_valid else None, set_offroad_alert_if_changed) msg.deviceState.screenBrightnessPercent = HARDWARE.get_screen_brightness() diff --git a/iqpilot/tools/iqpilot/mici_preview.py b/iqpilot/tools/iqpilot/mici_preview.py index 37da66c2c..5f43bb715 100755 --- a/iqpilot/tools/iqpilot/mici_preview.py +++ b/iqpilot/tools/iqpilot/mici_preview.py @@ -207,10 +207,20 @@ def _patch_mock_state(): activeBundle = _MockBundle() 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: """Minimal SubMaster-like dict that returns sensible defaults.""" _data = { "iqModelManager": _MockModelManager(), + "deviceState": _MockDeviceState(), } def __getitem__(self, key): return self._data.get(key, type("Empty", (), {"enabled": False})()) @@ -330,6 +340,16 @@ def load_panel(name: str): widget = cls() widget.set_rect(rect) 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