Compare commits

..

3 Commits

Author SHA1 Message Date
royjr 311d7b2fee reboot 2026-09-06 21:52:18 -04:00
royjr 71c1645157 Update settings.py 2026-09-06 21:52:18 -04:00
royjr a26939ea86 test 2026-09-06 21:52:18 -04:00
8 changed files with 56 additions and 241 deletions
-82
View File
@@ -1,82 +0,0 @@
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
@@ -62,7 +62,25 @@ class MiciFccModal(NavRawScrollPanel):
rl.draw_texture_ex(self._fcc_logo, fcc_pos, 0.0, 1.0, rl.WHITE) rl.draw_texture_ex(self._fcc_logo, fcc_pos, 0.0, 1.0, rl.WHITE)
def _engaged_confirmation_click(callback: Callable, action_text: str, icon: rl.Texture, exit_on_confirm: bool = True, red: bool = False): class DisengageDialog(BigDialog):
def __init__(self, title: str, description: str, disengaged_callback: Callable[[], None]):
super().__init__(title, description)
self._disengaged_callback = disengaged_callback
def _update_state(self):
super()._update_state()
if not ui_state.engaged and not self.is_dismissing:
self.dismiss(self._disengaged_callback)
class EngagedConfirmationDialog(BigConfirmationDialog):
def _update_state(self):
super()._update_state()
if ui_state.engaged and not self.is_dismissing:
self.dismiss()
def engaged_confirmation_click(callback: Callable, action_text: str, icon: rl.Texture, exit_on_confirm: bool = True, red: bool = False):
if not ui_state.engaged: if not ui_state.engaged:
def confirm_callback(): def confirm_callback():
# Check engaged again in case it changed while the dialog was open # Check engaged again in case it changed while the dialog was open
@@ -70,23 +88,26 @@ def _engaged_confirmation_click(callback: Callable, action_text: str, icon: rl.T
if not ui_state.engaged: if not ui_state.engaged:
callback() callback()
gui_app.push_widget(BigConfirmationDialog(f"slide to\n{action_text.lower()}", icon, confirm_callback, exit_on_confirm=exit_on_confirm, red=red)) gui_app.push_widget(EngagedConfirmationDialog(f"slide to\n{action_text.lower()}", icon, confirm_callback,
exit_on_confirm=exit_on_confirm, red=red))
else: else:
gui_app.push_widget(BigDialog("", f"Disengage to {action_text}")) gui_app.push_widget(DisengageDialog("", f"Disengage to {action_text}",
lambda: engaged_confirmation_click(callback, action_text, icon,
exit_on_confirm=exit_on_confirm, red=red)))
class EngagedConfirmationCircleButton(BigCircleButton): class EngagedConfirmationCircleButton(BigCircleButton):
def __init__(self, title: str, icon: rl.Texture, callback: Callable[[], None], exit_on_confirm: bool = True, def __init__(self, title: str, icon: rl.Texture, callback: Callable[[], None], exit_on_confirm: bool = True,
red: bool = False, icon_offset: tuple[int, int] = (0, 0)): red: bool = False, icon_offset: tuple[int, int] = (0, 0)):
super().__init__(icon, red, icon_offset) super().__init__(icon, red, icon_offset)
self.set_click_callback(lambda: _engaged_confirmation_click(callback, title, icon, exit_on_confirm=exit_on_confirm, red=red)) self.set_click_callback(lambda: engaged_confirmation_click(callback, title, icon, exit_on_confirm=exit_on_confirm, red=red))
class EngagedConfirmationButton(BigButton): class EngagedConfirmationButton(BigButton):
def __init__(self, text: str, action_text: str, icon: rl.Texture, callback: Callable[[], None], def __init__(self, text: str, action_text: str, icon: rl.Texture, callback: Callable[[], None],
exit_on_confirm: bool = True, red: bool = False): exit_on_confirm: bool = True, red: bool = False):
super().__init__(text, "", icon) super().__init__(text, "", icon)
self.set_click_callback(lambda: _engaged_confirmation_click(callback, action_text, icon, exit_on_confirm=exit_on_confirm, red=red)) self.set_click_callback(lambda: engaged_confirmation_click(callback, action_text, icon, exit_on_confirm=exit_on_confirm, red=red))
class DeviceInfoLayoutMici(Widget): class DeviceInfoLayoutMici(Widget):
@@ -6,9 +6,9 @@ See the LICENSE.md file in the root directory for more details.
""" """
from openpilot.selfdrive.ui.mici.layouts.settings import settings as OP from openpilot.selfdrive.ui.mici.layouts.settings import settings as OP
from openpilot.selfdrive.ui.mici.layouts.settings.settings import SettingsBigButton from openpilot.selfdrive.ui.mici.layouts.settings.settings import SettingsBigButton
from openpilot.selfdrive.ui.mici.layouts.settings.device import DeviceLayoutMici from openpilot.selfdrive.ui.mici.layouts.settings.device import DeviceLayoutMici, engaged_confirmation_click
from openpilot.selfdrive.ui.mici.widgets.button import BigCircleButton from openpilot.selfdrive.ui.mici.widgets.button import BigCircleButton
from openpilot.selfdrive.ui.mici.widgets.dialog import BigConfirmationDialog, BigDialog from openpilot.selfdrive.ui.mici.widgets.dialog import BigConfirmationDialog
from openpilot.selfdrive.ui.sunnypilot.mici.layouts.sunnylink import SunnylinkLayoutMici from openpilot.selfdrive.ui.sunnypilot.mici.layouts.sunnylink import SunnylinkLayoutMici
from openpilot.selfdrive.ui.sunnypilot.mici.layouts.models import ModelsLayoutMici from openpilot.selfdrive.ui.sunnypilot.mici.layouts.models import ModelsLayoutMici
from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.selfdrive.ui.ui_state import ui_state
@@ -93,10 +93,6 @@ class SettingsLayoutSP(OP.SettingsLayout):
dlg = BigConfirmationDialog(tr("slide to exit always offroad"), self.icon_offroad_slider, red=False, dlg = BigConfirmationDialog(tr("slide to exit always offroad"), self.icon_offroad_slider, red=False,
confirm_callback=lambda: _set_offroad_status(False)) confirm_callback=lambda: _set_offroad_status(False))
else: else:
if ui_state.engaged: engaged_confirmation_click(lambda: _set_offroad_status(True), "force offroad", self.icon_offroad_slider, red=True)
gui_app.push_widget(BigDialog(tr("disengage to enable always offroad"), "", )) return
return
dlg = BigConfirmationDialog(tr("slide to force offroad"), self.icon_offroad_slider, red=True,
confirm_callback=lambda: _set_offroad_status(True))
gui_app.push_widget(dlg) gui_app.push_widget(dlg)
-100
View File
@@ -1,100 +0,0 @@
"""
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
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()
+1 -2
View File
@@ -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 from openpilot.selfdrive.modeld.helpers import chestnut_present, load_oob
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,7 +52,6 @@ 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
@@ -1,43 +0,0 @@
"""
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
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"
@@ -0,0 +1,24 @@
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.")