mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-09-09 11:33:42 +08:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 215cee1d7d | |||
| cb707bef83 | |||
| 0dc286ff8f | |||
| 3615762b6d | |||
| 890c4ef336 | |||
| 8579eaf57f | |||
| 9e1a22b813 | |||
| 48aab1bc1d | |||
| 67436ab555 | |||
| 356bd1a96e |
@@ -0,0 +1,82 @@
|
|||||||
|
name: Test Models Compatibility With Tinygrad Changes
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- 'tinygrad_repo'
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- 'tinygrad_repo'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
generate-matrix:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
models: ${{ steps.set-matrix.outputs.models }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Fetch and Parse json
|
||||||
|
id: set-matrix
|
||||||
|
run: |
|
||||||
|
python3 -c '
|
||||||
|
import json, urllib.request, os, re
|
||||||
|
|
||||||
|
with open("openpilot/sunnypilot/models/fetcher.py", "r") as f:
|
||||||
|
urls = re.findall(r"MODEL_URL(?:_CHESTNUT)?\s*=\s*[\"'"'"']([^\"'"'"']+)[\"'"'"']", f.read())
|
||||||
|
|
||||||
|
artifacts = []
|
||||||
|
for url in urls:
|
||||||
|
data = json.loads(urllib.request.urlopen(url).read())
|
||||||
|
for bundle in data.get("bundles", []):
|
||||||
|
for model in bundle.get("models", []):
|
||||||
|
if "artifact" in model:
|
||||||
|
artifacts.append(model["artifact"])
|
||||||
|
|
||||||
|
with open(os.environ["GITHUB_OUTPUT"], "a") as f:
|
||||||
|
f.write(f"models={json.dumps(artifacts)}\n")
|
||||||
|
'
|
||||||
|
|
||||||
|
test-model:
|
||||||
|
name: Test ${{ matrix.artifact.file_name }}
|
||||||
|
needs: generate-matrix
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container: ghcr.io/commaai/openpilot-base:latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
artifact: ${{ fromJson(needs.generate-matrix.outputs.models) }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
|
||||||
|
- name: Download Model Chunks in Parallel
|
||||||
|
run: |
|
||||||
|
mkdir -p /tmp/model_chunks
|
||||||
|
echo '${{ toJson(matrix.artifact.chunks) }}' > chunks.json
|
||||||
|
|
||||||
|
BASE_URL="${{ matrix.artifact.download_uri.url }}"
|
||||||
|
export BASE_DIR=$(dirname "$BASE_URL")
|
||||||
|
|
||||||
|
python3 -c '
|
||||||
|
import json, os
|
||||||
|
with open("chunks.json") as f:
|
||||||
|
chunks = json.load(f)
|
||||||
|
manifest_path = f"/tmp/model_chunks/${{ matrix.artifact.file_name }}.chunkmanifest"
|
||||||
|
with open(manifest_path, "w") as f:
|
||||||
|
f.write(str(len(chunks)))
|
||||||
|
base_dir = os.environ["BASE_DIR"]
|
||||||
|
with open("/tmp/curl_config.txt", "w") as f:
|
||||||
|
for c in chunks:
|
||||||
|
fn = c["file_name"]
|
||||||
|
f.write(f"url = \"{base_dir}/{fn}\"\noutput = \"/tmp/model_chunks/{fn}\"\n")
|
||||||
|
'
|
||||||
|
curl -Z --parallel-immediate --parallel-max 16 -s -S -f -L -K /tmp/curl_config.txt
|
||||||
|
|
||||||
|
- name: Run Model Compatibility Test
|
||||||
|
env:
|
||||||
|
MODEL_BASE_NAME: ${{ matrix.artifact.file_name }}
|
||||||
|
MODEL_CHUNK_DIR: "/tmp/model_chunks"
|
||||||
|
PYTHONPATH: ".:./tinygrad_repo"
|
||||||
|
run: |
|
||||||
|
python3 -m pytest openpilot/sunnypilot/modeld_v2/tests/test_models.py
|
||||||
@@ -139,7 +139,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
{"ChestnutActive", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION | CLEAR_ON_IGNITION_ON, BOOL}},
|
{"ChestnutActive", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION | CLEAR_ON_IGNITION_ON, BOOL}},
|
||||||
{"ChestnutLoading", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION | CLEAR_ON_IGNITION_ON, BOOL}},
|
{"ChestnutLoading", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION | CLEAR_ON_IGNITION_ON, BOOL}},
|
||||||
{"ChestnutModelError", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION | CLEAR_ON_IGNITION_ON, BOOL}},
|
{"ChestnutModelError", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION | CLEAR_ON_IGNITION_ON, BOOL}},
|
||||||
{"AuxPowerSave", {PERSISTENT | BACKUP, BOOL}},
|
|
||||||
{"Version", {PERSISTENT, STRING}},
|
{"Version", {PERSISTENT, STRING}},
|
||||||
|
|
||||||
// --- sunnypilot params --- //
|
// --- sunnypilot params --- //
|
||||||
|
|||||||
@@ -225,7 +225,6 @@ class UIState(UIStateSP):
|
|||||||
ChestnutState.UNCOMPILED if detected else ChestnutState.DISCONNECTED)
|
ChestnutState.UNCOMPILED if detected else ChestnutState.DISCONNECTED)
|
||||||
return
|
return
|
||||||
|
|
||||||
self.chestnut_present = self.chestnut_present or detected
|
|
||||||
model_seen = self.sm.recv_frame["modelV2"] > self.started_frame
|
model_seen = self.sm.recv_frame["modelV2"] > self.started_frame
|
||||||
if not self.chestnut_present:
|
if not self.chestnut_present:
|
||||||
self.chestnut_state = ChestnutState.DISCONNECTED
|
self.chestnut_state = ChestnutState.DISCONNECTED
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
import io
|
||||||
|
import struct
|
||||||
|
import pickle
|
||||||
|
import inspect
|
||||||
|
import importlib
|
||||||
|
import enum
|
||||||
|
|
||||||
|
|
||||||
|
def _pad_args(func, args, kwargs):
|
||||||
|
try:
|
||||||
|
sig = inspect.signature(func)
|
||||||
|
except Exception:
|
||||||
|
return args, kwargs
|
||||||
|
params = list(sig.parameters.values())
|
||||||
|
if inspect.isfunction(func) and params and params[0].name in ('cls', 'self'):
|
||||||
|
params = params[1:]
|
||||||
|
|
||||||
|
new_args = list(args)
|
||||||
|
has_varargs = any(p.kind == inspect.Parameter.VAR_POSITIONAL for p in params)
|
||||||
|
if len(new_args) > len(params) and not has_varargs:
|
||||||
|
new_args = new_args[:len(params)]
|
||||||
|
|
||||||
|
for i in range(len(new_args), len(params)):
|
||||||
|
param = params[i]
|
||||||
|
if param.kind in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD):
|
||||||
|
continue
|
||||||
|
val = param.default if param.default is not inspect.Parameter.empty else None
|
||||||
|
new_args.append(val)
|
||||||
|
return new_args, kwargs
|
||||||
|
|
||||||
|
|
||||||
|
def _enum_factory(enum_class):
|
||||||
|
def factory(*args, **kwargs):
|
||||||
|
try:
|
||||||
|
return enum_class(*args, **kwargs)
|
||||||
|
# OptOps and UOp objects in the .pkl are left over from the compilation phase,
|
||||||
|
# reassignment does nothing because they aren't tied to the execution graph
|
||||||
|
# It never executes or evaluates the UOp nodes again.
|
||||||
|
except ValueError:
|
||||||
|
return list(enum_class)[0]
|
||||||
|
factory.__name__ = enum_class.__name__
|
||||||
|
factory.__module__ = enum_class.__module__
|
||||||
|
return factory
|
||||||
|
|
||||||
|
|
||||||
|
def _dynamic_factory(real_class):
|
||||||
|
if isinstance(real_class, type) and issubclass(real_class, enum.Enum):
|
||||||
|
return _enum_factory(real_class)
|
||||||
|
|
||||||
|
def factory(*args, **kwargs):
|
||||||
|
try:
|
||||||
|
return real_class(*args, **kwargs)
|
||||||
|
except TypeError:
|
||||||
|
new_args, new_kwargs = _pad_args(real_class, args, kwargs)
|
||||||
|
return real_class(*new_args, **new_kwargs)
|
||||||
|
|
||||||
|
class DynamicMeta(type(real_class)):
|
||||||
|
def __call__(cls, *args, **kwargs):
|
||||||
|
return factory(*args, **kwargs)
|
||||||
|
|
||||||
|
class DynamicProxy(real_class, metaclass=DynamicMeta):
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
|
def __new__(cls, *args, **kwargs):
|
||||||
|
return factory(*args, **kwargs)
|
||||||
|
|
||||||
|
DynamicProxy.__name__ = real_class.__name__
|
||||||
|
DynamicProxy.__module__ = real_class.__module__
|
||||||
|
return DynamicProxy
|
||||||
|
|
||||||
|
|
||||||
|
class DynamicTinygradUnpickler(pickle.Unpickler):
|
||||||
|
def find_class(self, module, name):
|
||||||
|
if module == "tinygrad.ops":
|
||||||
|
try:
|
||||||
|
importlib.import_module("tinygrad.uops")
|
||||||
|
module = "tinygrad.uops"
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
real_class = getattr(importlib.import_module(module), name)
|
||||||
|
if module.startswith("tinygrad"):
|
||||||
|
return _dynamic_factory(real_class)
|
||||||
|
return real_class
|
||||||
|
|
||||||
|
|
||||||
|
def load_oob(f):
|
||||||
|
opcodes = f.read(struct.unpack('<q', f.read(8))[0])
|
||||||
|
def buffers():
|
||||||
|
while (h := f.read(8)):
|
||||||
|
pb = pickle.PickleBuffer(bytearray(struct.unpack('<q', h)[0]))
|
||||||
|
f.readinto(pb)
|
||||||
|
yield pb
|
||||||
|
return DynamicTinygradUnpickler(io.BytesIO(opcodes), buffers=buffers()).load()
|
||||||
@@ -17,7 +17,7 @@ from tinygrad.tensor import Tensor
|
|||||||
|
|
||||||
import openpilot.cereal.messaging as messaging
|
import openpilot.cereal.messaging as messaging
|
||||||
from openpilot.common.hardware import COMMA_HARDWARE
|
from openpilot.common.hardware import COMMA_HARDWARE
|
||||||
from openpilot.selfdrive.modeld.helpers import chestnut_present, load_oob
|
from openpilot.selfdrive.modeld.helpers import chestnut_present
|
||||||
from openpilot.cereal import log
|
from openpilot.cereal import log
|
||||||
from opendbc.car.structs import car
|
from opendbc.car.structs import car
|
||||||
from openpilot.cereal.services import SERVICE_LIST
|
from openpilot.cereal.services import SERVICE_LIST
|
||||||
@@ -52,6 +52,7 @@ from openpilot.sunnypilot.modeld_v2.compile_modeld import (derive_frame_skip, ma
|
|||||||
WARP_INPUTS, POLICY_INPUTS)
|
WARP_INPUTS, POLICY_INPUTS)
|
||||||
from openpilot.sunnypilot.livedelay.helpers import get_lat_delay
|
from openpilot.sunnypilot.livedelay.helpers import get_lat_delay
|
||||||
from openpilot.sunnypilot.modeld_v2.modeld_base import ModelStateBase
|
from openpilot.sunnypilot.modeld_v2.modeld_base import ModelStateBase
|
||||||
|
from openpilot.sunnypilot.modeld_v2.helpers import load_oob
|
||||||
from openpilot.sunnypilot.models.helpers import get_active_bundle
|
from openpilot.sunnypilot.models.helpers import get_active_bundle
|
||||||
from openpilot.sunnypilot.selfdrive.controls.lib.relc import RoadEdgeLaneChangeController
|
from openpilot.sunnypilot.selfdrive.controls.lib.relc import RoadEdgeLaneChangeController
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import os
|
||||||
|
import unittest
|
||||||
|
from unittest.mock import patch
|
||||||
|
from openpilot.common.file_chunker import open_file_chunked
|
||||||
|
from openpilot.sunnypilot.modeld_v2.helpers import load_oob
|
||||||
|
from tinygrad.device import Device
|
||||||
|
|
||||||
|
|
||||||
|
class TestLegacyModels(unittest.TestCase):
|
||||||
|
def test_legacy_model_load(self):
|
||||||
|
base_name = os.environ.get("MODEL_BASE_NAME")
|
||||||
|
if not base_name:
|
||||||
|
raise unittest.SkipTest("MODEL_BASE_NAME env var not set, skipping integration test.")
|
||||||
|
chunk_dir = os.environ.get("MODEL_CHUNK_DIR", "/tmp/model_chunks")
|
||||||
|
base_path = os.path.join(chunk_dir, base_name)
|
||||||
|
|
||||||
|
try:
|
||||||
|
f = open_file_chunked(base_path)
|
||||||
|
except Exception as error:
|
||||||
|
self.fail(f"Failed to open chunked file {base_path}: {error}")
|
||||||
|
self.addCleanup(f.close)
|
||||||
|
|
||||||
|
real_getitem = Device.__class__.__getitem__
|
||||||
|
|
||||||
|
def safe_getitem(device_self, ix):
|
||||||
|
if ix == "QCOM" and not os.path.exists("/dev/kgsl-3d0"):
|
||||||
|
return real_getitem(device_self, "CPU")
|
||||||
|
if ix == "AMD" and not os.path.exists("/dev/kfd"):
|
||||||
|
return real_getitem(device_self, "CPU")
|
||||||
|
return real_getitem(device_self, ix)
|
||||||
|
|
||||||
|
with patch.object(Device.__class__, "__getitem__", safe_getitem):
|
||||||
|
obj = load_oob(f)
|
||||||
|
|
||||||
|
assert isinstance(obj, dict), "Parsed object is not a dictionary"
|
||||||
|
assert "metadata" in obj, "Metadata key is missing"
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import requests
|
|
||||||
|
|
||||||
from openpilot.sunnypilot.models.tinygrad_ref import get_tinygrad_ref
|
|
||||||
from openpilot.sunnypilot.models.fetcher import ModelFetcher
|
|
||||||
from openpilot.common.test import OpenpilotTestCase
|
|
||||||
|
|
||||||
def fetch_tinygrad_ref():
|
|
||||||
response = requests.get(ModelFetcher.MODEL_URL, timeout=10)
|
|
||||||
response.raise_for_status()
|
|
||||||
json_data = response.json()
|
|
||||||
return json_data.get("tinygrad_ref")
|
|
||||||
|
|
||||||
|
|
||||||
class TestTinygradRef(OpenpilotTestCase):
|
|
||||||
def test_tinygrad_ref(self):
|
|
||||||
current_ref = get_tinygrad_ref()
|
|
||||||
remote_ref = fetch_tinygrad_ref()
|
|
||||||
assert remote_ref == current_ref, (
|
|
||||||
f"""tinygrad_repo ref does not match remote tinygrad_ref of current compiled driving models json.
|
|
||||||
Current: {current_ref}
|
|
||||||
Remote: {remote_ref}
|
|
||||||
Please run build-all workflow to update models."""
|
|
||||||
)
|
|
||||||
print("tinygrad_repo ref matches current compiled driving models json ref.")
|
|
||||||
@@ -1675,12 +1675,6 @@
|
|||||||
"widget": "toggle",
|
"widget": "toggle",
|
||||||
"title": "Onroad Uploads"
|
"title": "Onroad Uploads"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"key": "AuxPowerSave",
|
|
||||||
"widget": "toggle",
|
|
||||||
"title": "Disable Aux Port When Offroad",
|
|
||||||
"description": "Power off the aux USB-C port while offroad to save power. It powers back on automatically when you go onroad."
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"key": "MaxTimeOffroad",
|
"key": "MaxTimeOffroad",
|
||||||
"widget": "option",
|
"widget": "option",
|
||||||
|
|||||||
@@ -30,10 +30,6 @@ sections:
|
|||||||
- key: OnroadUploads
|
- key: OnroadUploads
|
||||||
widget: toggle
|
widget: toggle
|
||||||
title: Onroad Uploads
|
title: Onroad Uploads
|
||||||
- key: AuxPowerSave
|
|
||||||
widget: toggle
|
|
||||||
title: Disable Aux Port When Offroad
|
|
||||||
description: Power off the aux USB-C port while offroad to save power. It powers back on automatically when you go onroad.
|
|
||||||
- key: MaxTimeOffroad
|
- key: MaxTimeOffroad
|
||||||
widget: option
|
widget: option
|
||||||
title: Max Time Offroad
|
title: Max Time Offroad
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ from openpilot.common.hardware import HARDWARE, COMMA_HARDWARE
|
|||||||
from openpilot.common.basedir import BASEDIR
|
from openpilot.common.basedir import BASEDIR
|
||||||
from openpilot.common.git import get_short_branch
|
from openpilot.common.git import get_short_branch
|
||||||
from openpilot.common.hardware.usb import CHESTNUT_FW_VERSION, CHESTNUT_USB_PRODUCT, get_usb_state, get_usb_topology, is_chestnut_usb_id, set_usb_state
|
from openpilot.common.hardware.usb import CHESTNUT_FW_VERSION, CHESTNUT_USB_PRODUCT, get_usb_state, get_usb_topology, is_chestnut_usb_id, set_usb_state
|
||||||
from openpilot.system.hardware.chestnut.flash import VBUS_PATH
|
|
||||||
from openpilot.common.linux import LinuxSystemStats
|
from openpilot.common.linux import LinuxSystemStats
|
||||||
from openpilot.system.loggerd.config import get_available_percent
|
from openpilot.system.loggerd.config import get_available_percent
|
||||||
from openpilot.common.swaglog import cloudlog
|
from openpilot.common.swaglog import cloudlog
|
||||||
@@ -51,10 +50,6 @@ class Chestnut:
|
|||||||
self.last_attempt = 0.
|
self.last_attempt = 0.
|
||||||
self.flashed = False
|
self.flashed = False
|
||||||
self.mismatch = False
|
self.mismatch = False
|
||||||
self.vbus_on = None
|
|
||||||
self.params = Params()
|
|
||||||
self.powersave = False
|
|
||||||
self.last_offroad = None
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def failed(self) -> bool:
|
def failed(self) -> bool:
|
||||||
@@ -66,19 +61,9 @@ class Chestnut:
|
|||||||
cloudlog.event("chestnut flash done", returncode=ret.returncode, output=ret.stdout[-1000:], error=ret.returncode != 0)
|
cloudlog.event("chestnut flash done", returncode=ret.returncode, output=ret.stdout[-1000:], error=ret.returncode != 0)
|
||||||
self.flashed = ret.returncode == 0
|
self.flashed = ret.returncode == 0
|
||||||
|
|
||||||
def set_vbus(self, on: bool) -> None:
|
|
||||||
if on == self.vbus_on:
|
|
||||||
return
|
|
||||||
subprocess.run(["sudo", "tee", VBUS_PATH], input=b"1" if on else b"0", stdout=subprocess.DEVNULL, check=False)
|
|
||||||
self.vbus_on = on
|
|
||||||
|
|
||||||
def update(self, offroad: bool, usb_state: list[dict]) -> None:
|
def update(self, offroad: bool, usb_state: list[dict]) -> None:
|
||||||
self.mismatch = any(is_chestnut_usb_id(d["vendorId"], d["productId"], include_bootloader=True) and
|
self.mismatch = any(is_chestnut_usb_id(d["vendorId"], d["productId"], include_bootloader=True) and
|
||||||
d["product"] != CHESTNUT_USB_PRODUCT for d in usb_state)
|
d["product"] != CHESTNUT_USB_PRODUCT for d in usb_state)
|
||||||
if offroad != self.last_offroad:
|
|
||||||
self.powersave = self.params.get_bool("AuxPowerSave")
|
|
||||||
self.last_offroad = offroad
|
|
||||||
self.set_vbus((not offroad or self.mismatch) or not self.powersave)
|
|
||||||
if not self.mismatch:
|
if not self.mismatch:
|
||||||
self.flashed = False
|
self.flashed = False
|
||||||
return
|
return
|
||||||
|
|||||||
+1
-1
Submodule tinygrad_repo updated: e837e367aa...f6fc4e3f2c
Reference in New Issue
Block a user