mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-09-13 06:33:45 +08:00
usbgpu? egpu? nah, NUTS!!
This commit is contained in:
@@ -31,7 +31,7 @@ on:
|
||||
type: string
|
||||
default: ''
|
||||
target_hardware:
|
||||
description: 'Hardware target to compile for (qcom or usbgpu)'
|
||||
description: 'Hardware target to compile for (qcom or chestnut)'
|
||||
required: false
|
||||
type: string
|
||||
default: 'qcom'
|
||||
@@ -57,7 +57,7 @@ on:
|
||||
type: choice
|
||||
options:
|
||||
- qcom
|
||||
- usbgpu
|
||||
- chestnut
|
||||
default: 'qcom'
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ jobs:
|
||||
cat $GITHUB_OUTPUT
|
||||
- run: |
|
||||
cd ${{ github.workspace }}/openpilot/openpilot
|
||||
if [ "${{ inputs.target_hardware }}" != "usbgpu" ]; then
|
||||
if [ "${{ inputs.target_hardware }}" != "chestnut" ]; then
|
||||
git lfs pull -X "**/selfdrive/modeld/models/big_*.onnx,**/selfdrive/modeld/models/dmonitoring_*.onnx"
|
||||
rm -f selfdrive/modeld/models/big_*.onnx selfdrive/modeld/models/dmonitoring_*.onnx
|
||||
else
|
||||
@@ -121,7 +121,7 @@ jobs:
|
||||
if-no-files-found: error
|
||||
|
||||
build_model:
|
||||
runs-on: [self-hosted, usbgpu]
|
||||
runs-on: [self-hosted, chestnut]
|
||||
needs: get_model
|
||||
env:
|
||||
MODEL_NAME: ${{ inputs.custom_name || inputs.upstream_branch }} (${{ needs.get_model.outputs.model_date }})
|
||||
@@ -185,9 +185,9 @@ jobs:
|
||||
CAMERA_RES=$(python3 -c "from openpilot.common.transformations.camera import _ar_ox_fisheye as a, _os_fisheye as o; print(f'{a.width}x{a.height} {o.width}x{o.height}')")
|
||||
|
||||
TG_FLAGS_QCOM="DEV=QCOM IMAGE=1 FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 OPENPILOT_HACKS=1"
|
||||
if [ "${{ inputs.target_hardware }}" == "usbgpu" ]; then
|
||||
echo "USBGPU build"
|
||||
export USBGPU=1
|
||||
if [ "${{ inputs.target_hardware }}" == "chestnut" ]; then
|
||||
echo "CHESTNUT build"
|
||||
export CHESTNUT=1
|
||||
TG_FLAGS="DEBUG=1 DEV=USB+AMD:LLVM WARP_DEV=QCOM FLOAT16=1 JIT_BATCH_SIZE=0 GMMU=0 TC_OPT=2"
|
||||
OUTPUT_PKL="${{ env.MODELS_DIR }}/big_driving_tinygrad.pkl"
|
||||
else
|
||||
|
||||
@@ -195,15 +195,16 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
|
||||
// Model Manager params
|
||||
{"ModelManager_ActiveBundle", {PERSISTENT, JSON}},
|
||||
{"ModelManager_ActiveBundleUSBGPU", {PERSISTENT, JSON}},
|
||||
{"ModelManager_ActiveBundleUSBGPU", {PERSISTENT, JSON}}, //TODO-SP: kept for migration, remove on next sync?
|
||||
{"ModelManager_ActiveBundleChestnut", {PERSISTENT, JSON}},
|
||||
{"ModelManager_ActiveJson", {CLEAR_ON_MANAGER_START, JSON}},
|
||||
{"ModelManager_ClearCache", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||
{"ModelManager_DownloadRef", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, STRING}},
|
||||
{"ModelManager_Favs", {PERSISTENT | BACKUP, STRING}},
|
||||
{"ModelManager_LastSyncTime", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, INT, "0"}},
|
||||
{"ModelManager_LastSyncTime_USBGPU", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, INT, "0"}},
|
||||
{"ModelManager_LastSyncTime_Chestnut", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, INT, "0"}},
|
||||
{"ModelManager_ModelsCache", {PERSISTENT | BACKUP, JSON}},
|
||||
{"ModelManager_ModelsCache_USBGPU", {PERSISTENT | BACKUP, JSON}},
|
||||
{"ModelManager_ModelsCache_Chestnut", {PERSISTENT | BACKUP, JSON}},
|
||||
|
||||
// Neural Network Lateral Control
|
||||
{"NeuralNetworkLateralControl", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
|
||||
@@ -62,14 +62,14 @@ class ModelsLayout(Widget):
|
||||
self.big_model_item = ListItemSP(
|
||||
title=tr("Big Model"),
|
||||
action_item=ScrollingButtonAction(tr("SELECT")),
|
||||
callback=lambda: self._open_source_dialog("usbgpu")
|
||||
callback=lambda: self._open_source_dialog("chestnut")
|
||||
)
|
||||
|
||||
self.download_item = download_status_item(lambda: tr("Download") if self._downloading else tr("Model Status"))
|
||||
|
||||
self.refresh_item = button_item(tr("Refresh Model List"), tr("REFRESH"), "",
|
||||
lambda: (ui_state.params.put("ModelManager_LastSyncTime", 0),
|
||||
ui_state.params.put("ModelManager_LastSyncTime_USBGPU", 0),
|
||||
ui_state.params.put("ModelManager_LastSyncTime_Chestnut", 0),
|
||||
gui_app.push_widget(alert_dialog(tr("Fetching Latest Models")))))
|
||||
|
||||
self.clear_cache_item = ListItemSP(
|
||||
@@ -177,14 +177,14 @@ class ModelsLayout(Widget):
|
||||
big_state = big_model_state()
|
||||
carry_source, carry_internal, _ = carrying_model()
|
||||
segments = []
|
||||
for source, label in (("qcom", tr("small")), ("usbgpu", tr("big"))):
|
||||
for source, label in (("qcom", tr("small")), ("chestnut", tr("big"))):
|
||||
if segments:
|
||||
segments.append(("|", rl.GRAY, None, None))
|
||||
bundle = get_selected_bundle(ui_state.params, source)
|
||||
name = bundle.internalName if bundle else default_model_name(source)
|
||||
color = ON_COLOR if (source == carry_source and name == carry_internal) else rl.LIGHTGRAY
|
||||
name = "● " + name
|
||||
if source == "usbgpu":
|
||||
if source == "chestnut":
|
||||
if big_state == 'failed':
|
||||
color = rl.RED
|
||||
elif big_state == 'loading':
|
||||
@@ -210,8 +210,8 @@ class ModelsLayout(Widget):
|
||||
small (stock modeld), a custom big has no automatic fallback yet."""
|
||||
if not ui_state.chestnut_present:
|
||||
return ""
|
||||
big_bundle = get_selected_bundle(ui_state.params, "usbgpu")
|
||||
big_name = big_bundle.internalName if big_bundle else default_model_name("usbgpu")
|
||||
big_bundle = get_selected_bundle(ui_state.params, "chestnut")
|
||||
big_name = big_bundle.internalName if big_bundle else default_model_name("chestnut")
|
||||
big_is_default = big_bundle is None
|
||||
fallback_name = default_model_name("qcom")
|
||||
state = big_model_state()
|
||||
@@ -225,7 +225,7 @@ class ModelsLayout(Widget):
|
||||
return tr("Getting the big model ready.")
|
||||
if big_is_default:
|
||||
return tr("{} will drive. If it fails during a drive, {} takes over until the next drive.").format(big_name, fallback_name)
|
||||
return tr("{} will drive when the eGPU is ready.").format(big_name)
|
||||
return tr("{} will drive when the chestnut is ready.").format(big_name)
|
||||
|
||||
@staticmethod
|
||||
def _download_row_state(progresses, name: str) -> dict:
|
||||
@@ -261,7 +261,7 @@ class ModelsLayout(Widget):
|
||||
ui_state.params.put("ModelManager_DownloadRef", selected_bundle.ref)
|
||||
|
||||
def _resolve_selected_bundle(self, ref):
|
||||
source_bundles = {source: bundles_for_source(source) for source in ("qcom", "usbgpu")}
|
||||
source_bundles = {source: bundles_for_source(source) for source in ("qcom", "chestnut")}
|
||||
resolved = resolve_bundle_by_ref(ref, source_bundles)
|
||||
return resolved[0] if resolved else None
|
||||
|
||||
@@ -329,7 +329,7 @@ class ModelsLayout(Widget):
|
||||
self._handle_bundle_download_progress()
|
||||
|
||||
carry_source, _, carry_display = carrying_model()
|
||||
for item, item_source in ((self.small_model_item, "qcom"), (self.big_model_item, "usbgpu")):
|
||||
for item, item_source in ((self.small_model_item, "qcom"), (self.big_model_item, "chestnut")):
|
||||
bundle = get_selected_bundle(ui_state.params, item_source)
|
||||
name = bundle.displayName if bundle else default_model_name(item_source)
|
||||
color = ON_COLOR if (item_source == carry_source and name == carry_display) else style.ITEM_TEXT_VALUE_COLOR
|
||||
|
||||
@@ -21,8 +21,8 @@ METRIC_MARGIN = 30
|
||||
METRIC_START_Y = 300
|
||||
HOME_BTN = rl.Rectangle(60, 860, 180, 180)
|
||||
|
||||
EGPU_ICON_WIDTH = 180
|
||||
EGPU_ICON_HEIGHT = 133
|
||||
CHESTNUT_ICON_WIDTH = 180
|
||||
CHESTNUT_ICON_HEIGHT = 133
|
||||
|
||||
|
||||
# Color scheme
|
||||
@@ -59,11 +59,11 @@ class MetricData:
|
||||
class SidebarSP:
|
||||
def __init__(self):
|
||||
self._sunnylink_status = MetricData(tr_noop("SUNNYLINK"), tr_noop("OFFLINE"), Colors.WARNING)
|
||||
self._egpu_green_img = gui_app.texture("icons_mici/chestnut_green.png", EGPU_ICON_WIDTH, EGPU_ICON_HEIGHT)
|
||||
self._egpu_default_img = gui_app.texture("icons_mici/chestnut.png", EGPU_ICON_WIDTH, EGPU_ICON_HEIGHT)
|
||||
self._egpu_orange_img = gui_app.texture("icons_mici/chestnut_orange.png", EGPU_ICON_WIDTH, EGPU_ICON_HEIGHT)
|
||||
self._chestnut_green_img = gui_app.texture("icons_mici/chestnut_green.png", CHESTNUT_ICON_WIDTH, CHESTNUT_ICON_HEIGHT)
|
||||
self._chestnut_default_img = gui_app.texture("icons_mici/chestnut.png", CHESTNUT_ICON_WIDTH, CHESTNUT_ICON_HEIGHT)
|
||||
self._chestnut_orange_img = gui_app.texture("icons_mici/chestnut_orange.png", CHESTNUT_ICON_WIDTH, CHESTNUT_ICON_HEIGHT)
|
||||
# gray state uses the default art (chestnut_gray.png removed upstream)
|
||||
self._egpu_gray_img = gui_app.texture("icons_mici/chestnut.png", EGPU_ICON_WIDTH, EGPU_ICON_HEIGHT)
|
||||
self._chestnut_gray_img = gui_app.texture("icons_mici/chestnut.png", CHESTNUT_ICON_WIDTH, CHESTNUT_ICON_HEIGHT)
|
||||
|
||||
def _update_sunnylink_status(self):
|
||||
if not ui_state.params.get_bool("SunnylinkEnabled"):
|
||||
@@ -99,14 +99,14 @@ class SidebarSP:
|
||||
loading = ui_state.chestnut_loading or (big_model_selected and ui_state.started and ui_state.chestnut_active is None)
|
||||
|
||||
if loading:
|
||||
icon = self._egpu_default_img
|
||||
icon = self._chestnut_default_img
|
||||
opacity = 0.35 + 0.65 * (0.5 - 0.5 * math.cos(rl.get_time() * 6.0))
|
||||
elif big_model_selected and big_model_failed:
|
||||
icon, opacity = self._egpu_orange_img, 1.0
|
||||
icon, opacity = self._chestnut_orange_img, 1.0
|
||||
elif big_model_selected:
|
||||
icon, opacity = self._egpu_green_img, 1.0
|
||||
icon, opacity = self._chestnut_green_img, 1.0
|
||||
else:
|
||||
icon, opacity = self._egpu_gray_img, 1.0
|
||||
icon, opacity = self._chestnut_gray_img, 1.0
|
||||
|
||||
x = HOME_BTN.x + (HOME_BTN.width - icon.width) / 2
|
||||
y = HOME_BTN.y + (HOME_BTN.height - icon.height) / 2
|
||||
|
||||
@@ -27,14 +27,14 @@ def _model_info() -> tuple[str, str, str]:
|
||||
state = big_model_state()
|
||||
_, _, carry_display = carrying_model()
|
||||
if carry_display is None:
|
||||
big = get_selected_bundle(ui_state.params, "usbgpu")
|
||||
carry_display = big.displayName if big else default_model_name("usbgpu")
|
||||
big = get_selected_bundle(ui_state.params, "chestnut")
|
||||
carry_display = big.displayName if big else default_model_name("chestnut")
|
||||
active_text = (carry_display or active_name).lower()
|
||||
if state == 'failed':
|
||||
return active_text, tr("big model"), tr("unavailable")
|
||||
if state == 'loading':
|
||||
return active_text, tr("big model"), tr("getting ready")
|
||||
header = tr("small model") if source == "usbgpu" else tr("big model")
|
||||
header = tr("small model") if source == "chestnut" else tr("big model")
|
||||
return active_text, header, other_name.lower()
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ class ModelsLayoutMici(NavScroller):
|
||||
|
||||
hardware_btns = []
|
||||
active = active_source()
|
||||
for source, label in (("qcom", tr("small models")), ("usbgpu", tr("big models"))):
|
||||
for source, label in (("qcom", tr("small models")), ("chestnut", tr("big models"))):
|
||||
bundle = get_selected_bundle(ui_state.params, source)
|
||||
value = (bundle.internalName if bundle else default_model_name(source)).lower()
|
||||
if source == active:
|
||||
|
||||
@@ -11,8 +11,8 @@ from openpilot.sunnypilot.models.model_name import DEFAULT_BIG_MODEL, DEFAULT_MO
|
||||
|
||||
|
||||
def active_source() -> str:
|
||||
return get_active_source(usbgpu=ui_state.chestnut_present,
|
||||
usbgpu_active=ui_state.chestnut_active, usbgpu_loading=ui_state.chestnut_loading,
|
||||
return get_active_source(chestnut=ui_state.chestnut_present,
|
||||
chestnut_active=ui_state.chestnut_active, chestnut_loading=ui_state.chestnut_loading,
|
||||
offroad=ui_state.is_offroad())
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ def bundles_for_source(source: str):
|
||||
|
||||
|
||||
def default_model(source: str) -> str:
|
||||
return DEFAULT_BIG_MODEL if source == 'usbgpu' else DEFAULT_MODEL
|
||||
return DEFAULT_BIG_MODEL if source == 'chestnut' else DEFAULT_MODEL
|
||||
|
||||
|
||||
def default_model_name(source: str) -> str:
|
||||
@@ -45,14 +45,14 @@ def carrying_model() -> tuple[str | None, str | None, str | None]:
|
||||
when a Default big cannot carry, stock modeld runs the Default small, never the
|
||||
small slot's pick; a custom big has no automatic fallback yet -> (None, None, None)."""
|
||||
source = active_source()
|
||||
if source == "usbgpu":
|
||||
bundle = get_selected_bundle(ui_state.params, "usbgpu")
|
||||
if source == "chestnut":
|
||||
bundle = get_selected_bundle(ui_state.params, "chestnut")
|
||||
if bundle:
|
||||
return "usbgpu", bundle.internalName, bundle.displayName
|
||||
name = default_model_name("usbgpu")
|
||||
return "usbgpu", name, name
|
||||
return "chestnut", bundle.internalName, bundle.displayName
|
||||
name = default_model_name("chestnut")
|
||||
return "chestnut", name, name
|
||||
if ui_state.chestnut_present:
|
||||
if get_selected_bundle(ui_state.params, "usbgpu") is None:
|
||||
if get_selected_bundle(ui_state.params, "chestnut") is None:
|
||||
name = default_model_name("qcom")
|
||||
return "qcom", name, name
|
||||
return None, None, None
|
||||
@@ -66,7 +66,7 @@ def carrying_model() -> tuple[str | None, str | None, str | None]:
|
||||
def queued_name(current_ref) -> str | None:
|
||||
ref = ui_state.params.get("ModelManager_DownloadRef")
|
||||
if ref and ref != current_ref:
|
||||
source_bundles = {source: bundles_for_source(source) for source in ("qcom", "usbgpu")}
|
||||
source_bundles = {source: bundles_for_source(source) for source in ("qcom", "chestnut")}
|
||||
if resolved := resolve_bundle_by_ref(ref, source_bundles):
|
||||
return resolved[0].internalName
|
||||
return None
|
||||
@@ -79,7 +79,7 @@ def model_info() -> tuple[str, str, str]:
|
||||
manager republishes a tick after a chestnut change, so the stale bundle
|
||||
would flash the wrong model."""
|
||||
source = active_source()
|
||||
other = "qcom" if source == "usbgpu" else "usbgpu"
|
||||
other = "qcom" if source == "chestnut" else "chestnut"
|
||||
active_bundle = get_selected_bundle(ui_state.params, source)
|
||||
other_bundle = get_selected_bundle(ui_state.params, other)
|
||||
|
||||
|
||||
@@ -152,8 +152,8 @@ class UIStateSP:
|
||||
self.has_icbm = self.CP_SP.intelligentCruiseButtonManagementAvailable and self.params.get_bool("IntelligentCruiseButtonManagement")
|
||||
|
||||
self._enforce_constraints()
|
||||
source = get_active_source(usbgpu=self.chestnut_present, usbgpu_active=self.chestnut_active,
|
||||
usbgpu_loading=self.chestnut_loading, offroad=self.is_offroad())
|
||||
source = get_active_source(chestnut=self.chestnut_present, chestnut_active=self.chestnut_active,
|
||||
chestnut_loading=self.chestnut_loading, offroad=self.is_offroad())
|
||||
self.active_bundle = self.params.get(ACTIVE_BUNDLE_KEYS[source])
|
||||
self.model_runner_tinygrad = self.active_bundle is not None and self.active_bundle.get("runner") == "tinygrad"
|
||||
self.blindspot = self.params.get_bool("BlindSpot")
|
||||
|
||||
@@ -298,7 +298,7 @@ def _load_policy_runners(args: argparse.Namespace) -> tuple[list, list]:
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if 'USB' in os.getenv('DEV', '') or os.getenv('USBGPU'):
|
||||
if 'USB' in os.getenv('DEV', '') or os.getenv('CHESTNUT'):
|
||||
from openpilot.system.hardware.chestnut.flash import link_up
|
||||
for _ in range(10):
|
||||
if link_up():
|
||||
|
||||
@@ -84,14 +84,14 @@ class ModelState(ModelStateBase):
|
||||
inputs: dict[str, np.ndarray]
|
||||
prev_desire: np.ndarray
|
||||
|
||||
def __init__(self, cam_w: int, cam_h: int, usbgpu: bool = False):
|
||||
def __init__(self, cam_w: int, cam_h: int, chestnut: bool = False):
|
||||
ModelStateBase.__init__(self)
|
||||
|
||||
env_pkl = os.environ.get('COMBINED_MODEL_PKL')
|
||||
if env_pkl and os.path.exists(env_pkl):
|
||||
model_bundle = None
|
||||
else:
|
||||
model_bundle = get_active_bundle(usbgpu=usbgpu)
|
||||
model_bundle = get_active_bundle(chestnut=chestnut)
|
||||
self.generation = model_bundle.generation if model_bundle is not None else None
|
||||
overrides = {override.key: override.value for override in model_bundle.overrides} if model_bundle else {}
|
||||
|
||||
@@ -99,7 +99,7 @@ class ModelState(ModelStateBase):
|
||||
self.LONG_SMOOTH_SECONDS = float(overrides.get('long', ".0"))
|
||||
self.MIN_LAT_CONTROL_SPEED = 0.3
|
||||
self.PLANPLUS_CONTROL: float = 1.0
|
||||
self.usbgpu = usbgpu
|
||||
self.chestnut = chestnut
|
||||
|
||||
pkl_path = _find_driving_pkl(model_bundle)
|
||||
assert pkl_path is not None, "No driving pkl found — all models must be compiled with compile_modeld.py"
|
||||
@@ -110,7 +110,7 @@ class ModelState(ModelStateBase):
|
||||
jits = load_oob(open_file_chunked(pkl_path))
|
||||
|
||||
self.WARP_DEV = 'QCOM' if COMMA_HARDWARE else 'CPU'
|
||||
self.DEV = 'AMD' if self.usbgpu else self.WARP_DEV
|
||||
self.DEV = 'AMD' if self.chestnut else self.WARP_DEV
|
||||
self.QUEUE_DEV = self.DEV
|
||||
metadata = jits['metadata']
|
||||
|
||||
@@ -185,7 +185,7 @@ class ModelState(ModelStateBase):
|
||||
else:
|
||||
self.warp(**{k: self.input_queues[k] for k in WARP_INPUTS}, frame=frame_tensor, big_frame=big_frame_tensor)
|
||||
|
||||
if self.usbgpu:
|
||||
if self.chestnut:
|
||||
self.warmup()
|
||||
|
||||
def warmup(self) -> None:
|
||||
@@ -287,7 +287,7 @@ class ModelState(ModelStateBase):
|
||||
buf[0, :-1] = buf[0, 1:]
|
||||
buf[0, -1, :] = outputs['desired_curvature'][0, :] if not self.mlsim else 0
|
||||
|
||||
if self.usbgpu and not np.all(np.isfinite(outputs.get('plan', np.array([0.])))):
|
||||
if self.chestnut and not np.all(np.isfinite(outputs.get('plan', np.array([0.])))):
|
||||
cloudlog.error("model output not finite, dropping frame")
|
||||
return None
|
||||
|
||||
@@ -327,12 +327,12 @@ def main(demo=False):
|
||||
setproctitle(PROCESS_NAME)
|
||||
config_realtime_process(7, 54)
|
||||
|
||||
USBGPU = chestnut_present()
|
||||
if USBGPU:
|
||||
CHESTNUT = chestnut_present()
|
||||
if CHESTNUT:
|
||||
os.environ['HCQDEV_WAIT_TIMEOUT_MS'] = '3000'
|
||||
|
||||
params = Params()
|
||||
params.put_bool("ChestnutLoading", USBGPU)
|
||||
params.put_bool("ChestnutLoading", CHESTNUT)
|
||||
params.remove("ChestnutActive")
|
||||
|
||||
# visionipc clients
|
||||
@@ -362,31 +362,31 @@ def main(demo=False):
|
||||
st = time.monotonic()
|
||||
|
||||
model = None
|
||||
if USBGPU:
|
||||
if CHESTNUT:
|
||||
import threading
|
||||
def load():
|
||||
nonlocal model
|
||||
model = ModelState(cam_w=vipc_client_main.width, cam_h=vipc_client_main.height, usbgpu=True)
|
||||
model = ModelState(cam_w=vipc_client_main.width, cam_h=vipc_client_main.height, chestnut=True)
|
||||
t = threading.Thread(target=load, daemon=True)
|
||||
t.start()
|
||||
t.join(60)
|
||||
if model is None:
|
||||
params.put_bool("ChestnutActive", False)
|
||||
raise RuntimeError("eGPU model load failed or timed out (60s)")
|
||||
raise RuntimeError("chestnut model load failed or timed out (60s)")
|
||||
params.put_bool("ChestnutActive", True)
|
||||
else:
|
||||
model = ModelState(cam_w=vipc_client_main.width, cam_h=vipc_client_main.height, usbgpu=False)
|
||||
model = ModelState(cam_w=vipc_client_main.width, cam_h=vipc_client_main.height, chestnut=False)
|
||||
|
||||
params.put_bool("ChestnutLoading", False)
|
||||
cloudlog.warning(f"models loaded in {time.monotonic() - st:.1f}s, modeld starting")
|
||||
|
||||
# messaging
|
||||
pub_socks = ["modelV2", "drivingModelData", "cameraOdometry", "modelDataV2SP"] + (["chestnutState"] if USBGPU else [])
|
||||
pub_socks = ["modelV2", "drivingModelData", "cameraOdometry", "modelDataV2SP"] + (["chestnutState"] if CHESTNUT else [])
|
||||
pm = PubMaster(pub_socks)
|
||||
sm = SubMaster(["deviceState", "carState", "narrowRoadCameraState", "extrinsicsCalibration", "driverMonitoringState", "carControl", "lateralDelay"])
|
||||
|
||||
publish_state = PublishState()
|
||||
chestnut_state = ChestnutState(pm, USBGPU) if USBGPU else None
|
||||
chestnut_state = ChestnutState(pm, CHESTNUT) if CHESTNUT else None
|
||||
|
||||
# setup filter to track dropped frames
|
||||
frame_dropped_filter = FirstOrderFilter(0., 10., 1. / model.constants.MODEL_FREQ)
|
||||
@@ -524,7 +524,7 @@ def main(demo=False):
|
||||
fill_model_msg(drivingdata_send, modelv2_send, model_output, action,
|
||||
publish_state, meta_main.frame_id, meta_extra.frame_id, frame_id,
|
||||
frame_drop_ratio, meta_main.timestamp_eof, model_execution_time, live_calib_seen, meta_constants)
|
||||
modelv2_send.modelV2.big = model.usbgpu
|
||||
modelv2_send.modelV2.big = model.chestnut
|
||||
|
||||
desire_state = modelv2_send.modelV2.meta.desireState
|
||||
l_lane_change_prob = desire_state[log.Desire.laneChangeLeft]
|
||||
|
||||
@@ -190,8 +190,8 @@ def tmp_path():
|
||||
|
||||
def patch_modeld(monkeypatch):
|
||||
def _patch(bundle):
|
||||
monkeypatch.setattr(helpers, 'get_active_bundle', lambda params=None, *, usbgpu=None: bundle)
|
||||
monkeypatch.setattr(modeld_module, 'get_active_bundle', lambda params=None, *, usbgpu=None: bundle)
|
||||
monkeypatch.setattr(helpers, 'get_active_bundle', lambda params=None, *, chestnut=None: bundle)
|
||||
monkeypatch.setattr(modeld_module, 'get_active_bundle', lambda params=None, *, chestnut=None: bundle)
|
||||
|
||||
return _patch
|
||||
|
||||
|
||||
@@ -59,8 +59,8 @@ class TestFindDrivingPkl(OpenpilotTestCase):
|
||||
class TestModelStateCombinedInit(OpenpilotTestCase):
|
||||
def test_asserts_when_no_pkl(self, monkeypatch):
|
||||
bundle = DummyBundle(models=[], is_20hz=True)
|
||||
monkeypatch.setattr(helpers, 'get_active_bundle', lambda params=None, *, usbgpu=None: bundle)
|
||||
monkeypatch.setattr(modeld_module, 'get_active_bundle', lambda params=None, *, usbgpu=None: bundle)
|
||||
monkeypatch.setattr(helpers, 'get_active_bundle', lambda params=None, *, chestnut=None: bundle)
|
||||
monkeypatch.setattr(modeld_module, 'get_active_bundle', lambda params=None, *, chestnut=None: bundle)
|
||||
with self.assertRaisesRegex(AssertionError, "No driving pkl found"):
|
||||
ModelState(cam_w=CAM_W, cam_h=CAM_H)
|
||||
|
||||
|
||||
@@ -139,11 +139,11 @@ class ModelCache:
|
||||
class ModelFetcher:
|
||||
"""Handles fetching and caching of model data from remote source"""
|
||||
MODEL_URL = "https://raw.githubusercontent.com/sunnypilot/sunnypilot-models/refs/heads/gh-pages/docs/driving_models_v21.json"
|
||||
MODEL_URL_USBGPU = "https://raw.githubusercontent.com/sunnypilot/sunnypilot-models/refs/heads/gh-pages/docs/driving_models_usbgpu_v22.json"
|
||||
MODEL_URL_CHESTNUT = "https://raw.githubusercontent.com/sunnypilot/sunnypilot-models/refs/heads/gh-pages/docs/driving_models_chestnut_v22.json"
|
||||
|
||||
MODEL_SOURCES = {
|
||||
"qcom": (MODEL_URL, ""),
|
||||
"usbgpu": (MODEL_URL_USBGPU, "_USBGPU"),
|
||||
"chestnut": (MODEL_URL_CHESTNUT, "_Chestnut"),
|
||||
}
|
||||
|
||||
def __init__(self, params: Params):
|
||||
@@ -156,12 +156,12 @@ class ModelFetcher:
|
||||
self._refetched: set[str] = set()
|
||||
self.params.put("ModelManager_ActiveJson", {
|
||||
"qcom": self.MODEL_URL,
|
||||
"usbgpu": self.MODEL_URL_USBGPU,
|
||||
"chestnut": self.MODEL_URL_CHESTNUT,
|
||||
}, block=True)
|
||||
|
||||
@staticmethod
|
||||
def active_source(chestnut_present: bool) -> str:
|
||||
return "usbgpu" if chestnut_present else "qcom"
|
||||
return "chestnut" if chestnut_present else "qcom"
|
||||
|
||||
def _fetch_and_cache_models(self, source: str) -> list[custom.ModelManagerSP.ModelBundle] | None:
|
||||
"""Fetches fresh model data from remote and updates cache.
|
||||
@@ -200,7 +200,7 @@ class ModelFetcher:
|
||||
@staticmethod
|
||||
def _cache_matches_source(source: str, cached_data: dict) -> bool:
|
||||
bundles = cached_data.get("bundles", [])
|
||||
if source == "usbgpu":
|
||||
if source == "chestnut":
|
||||
return any(bundle.get("is_big") is True for bundle in bundles)
|
||||
return not any(bundle.get("is_big") is True for bundle in bundles)
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ ModelManager = custom.ModelManagerSP
|
||||
|
||||
ACTIVE_BUNDLE_KEYS = {
|
||||
"qcom": "ModelManager_ActiveBundle",
|
||||
"usbgpu": "ModelManager_ActiveBundleUSBGPU",
|
||||
"chestnut": "ModelManager_ActiveBundleChestnut",
|
||||
}
|
||||
_LAST_VALIDATED_RAW: dict[str, dict | None] = {}
|
||||
|
||||
@@ -126,20 +126,20 @@ def get_selected_bundle(params: Params | None = None, source: str = "qcom") -> "
|
||||
return _parse_active_bundle(params.get(ACTIVE_BUNDLE_KEYS[source]))
|
||||
|
||||
|
||||
def get_active_source(usbgpu: bool | None = None, usbgpu_active: bool | None = None,
|
||||
usbgpu_loading: bool | None = None, offroad: bool | None = None) -> str:
|
||||
if usbgpu is None:
|
||||
usbgpu = chestnut_present()
|
||||
state_valid = usbgpu_active is not None or usbgpu_loading is not None or offroad is not None
|
||||
big_active = usbgpu and (not state_valid or usbgpu_active or usbgpu_loading or offroad)
|
||||
return "usbgpu" if big_active else "qcom"
|
||||
def get_active_source(chestnut: bool | None = None, chestnut_active: bool | None = None,
|
||||
chestnut_loading: bool | None = None, offroad: bool | None = None) -> str:
|
||||
if chestnut is None:
|
||||
chestnut = chestnut_present()
|
||||
state_valid = chestnut_active is not None or chestnut_loading is not None or offroad is not None
|
||||
big_active = chestnut and (not state_valid or chestnut_active or chestnut_loading or offroad)
|
||||
return "chestnut" if big_active else "qcom"
|
||||
|
||||
|
||||
def get_active_bundle(params: Params | None = None, *, usbgpu: bool | None = None) -> "custom.ModelManagerSP.ModelBundle | None":
|
||||
def get_active_bundle(params: Params | None = None, *, chestnut: bool | None = None) -> "custom.ModelManagerSP.ModelBundle | None":
|
||||
# no cross-slot fallback: an empty active slot means the hardware default, which
|
||||
# only stock modeld can run - modeld_v2 requires a real bundle
|
||||
params = params or Params()
|
||||
return get_selected_bundle(params, get_active_source(usbgpu=usbgpu))
|
||||
return get_selected_bundle(params, get_active_source(chestnut=chestnut))
|
||||
|
||||
|
||||
def resolve_bundle_by_ref(
|
||||
|
||||
@@ -40,7 +40,7 @@ class ModelManagerSP:
|
||||
self.available_models: list[custom.ModelManagerSP.ModelBundle] = []
|
||||
self.source_models: dict[str, list[custom.ModelManagerSP.ModelBundle]] = {}
|
||||
self.selected_bundle: custom.ModelManagerSP.ModelBundle = None
|
||||
self.active_bundle: custom.ModelManagerSP.ModelBundle = get_active_bundle(self.params, usbgpu=self.chestnut_present)
|
||||
self.active_bundle: custom.ModelManagerSP.ModelBundle = get_active_bundle(self.params, chestnut=self.chestnut_present)
|
||||
self._chunk_size = 128 * 1000 # 128 KB chunks
|
||||
self._download_start_times: dict[str, float] = {} # Track start time per model
|
||||
self._download_ref: bytes | str | None = None
|
||||
@@ -280,7 +280,7 @@ class ModelManagerSP:
|
||||
raise DownloadCancelled("Download cancelled")
|
||||
self.selected_bundle.status = custom.ModelManagerSP.DownloadStatus.downloaded
|
||||
self.params.put(ACTIVE_BUNDLE_KEYS[source], model_bundle.to_dict(), block=True)
|
||||
self.active_bundle = get_active_bundle(self.params, usbgpu=self.chestnut_present)
|
||||
self.active_bundle = get_active_bundle(self.params, chestnut=self.chestnut_present)
|
||||
|
||||
except Exception:
|
||||
if self.selected_bundle is not None:
|
||||
@@ -326,7 +326,7 @@ class ModelManagerSP:
|
||||
self.source_models = {source: self.model_fetcher.get_bundles_for_source(source) for source in ModelFetcher.MODEL_SOURCES}
|
||||
self.available_models = self.source_models[ModelFetcher.active_source(self.chestnut_present)]
|
||||
validate_active_bundles(self.params, self.source_models)
|
||||
self.active_bundle = get_active_bundle(self.params, usbgpu=self.chestnut_present)
|
||||
self.active_bundle = get_active_bundle(self.params, chestnut=self.chestnut_present)
|
||||
|
||||
self._process_download_requests()
|
||||
|
||||
|
||||
@@ -392,7 +392,7 @@ class TestManagerDownload(ManagerDownloadTestBase):
|
||||
asyncio.run(self.manager._download_bundle(self._bundle, self.dest, "qcom"))
|
||||
|
||||
assert "ModelManager_ActiveBundle" in store, "qcom download must write the qcom slot"
|
||||
assert "ModelManager_ActiveBundleUSBGPU" not in store, "qcom download must not touch the usbgpu slot"
|
||||
assert "ModelManager_ActiveBundleChestnut" not in store, "qcom download must not touch the chestnut slot"
|
||||
assert self.manager.selected_bundle.status == custom.ModelManagerSP.DownloadStatus.downloaded
|
||||
assert self.manager.active_bundle is not None and self.manager.active_bundle.ref == "test-ref"
|
||||
assert self.manager.active_bundle.status == custom.ModelManagerSP.DownloadStatus.downloaded
|
||||
@@ -401,18 +401,18 @@ class TestManagerDownload(ManagerDownloadTestBase):
|
||||
assert missing == [], f"chunks missing from the cache: {missing}"
|
||||
self.run_with_server(body)
|
||||
|
||||
def test_download_writes_usbgpu_slot(self):
|
||||
"""A download resolved to the usbgpu source writes the usbgpu active bundle slot only."""
|
||||
def test_download_writes_chestnut_slot(self):
|
||||
"""A download resolved to the chestnut source writes the chestnut active bundle slot only."""
|
||||
def body():
|
||||
self.make_artifact(chunked=True)
|
||||
self._bundle.ref = "big-ref"
|
||||
self._bundle.minimumSelectorVersion = 18
|
||||
params, store = self._make_params_with_store()
|
||||
self.manager.params = params
|
||||
asyncio.run(self.manager._download_bundle(self._bundle, self.dest, "usbgpu"))
|
||||
asyncio.run(self.manager._download_bundle(self._bundle, self.dest, "chestnut"))
|
||||
|
||||
assert "ModelManager_ActiveBundleUSBGPU" in store, "usbgpu download must write the usbgpu slot"
|
||||
assert "ModelManager_ActiveBundle" not in store, "usbgpu download must not touch the qcom slot"
|
||||
assert "ModelManager_ActiveBundleChestnut" in store, "chestnut download must write the chestnut slot"
|
||||
assert "ModelManager_ActiveBundle" not in store, "chestnut download must not touch the qcom slot"
|
||||
assert self.manager.selected_bundle.status == custom.ModelManagerSP.DownloadStatus.downloaded
|
||||
self.run_with_server(body)
|
||||
|
||||
@@ -447,20 +447,20 @@ class TestResolveBundleByRef(OpenpilotTestCase):
|
||||
|
||||
def test_qcom_ref_resolves_to_qcom_slot(self):
|
||||
small = self._bundle("small")
|
||||
assert resolve_bundle_by_ref("small", {"qcom": [small], "usbgpu": []}) == (small, "qcom")
|
||||
assert resolve_bundle_by_ref("small", {"qcom": [small], "chestnut": []}) == (small, "qcom")
|
||||
|
||||
def test_usbgpu_ref_resolves_to_usbgpu_slot(self):
|
||||
def test_chestnut_ref_resolves_to_chestnut_slot(self):
|
||||
big = self._bundle("big")
|
||||
assert resolve_bundle_by_ref("big", {"qcom": [], "usbgpu": [big]}) == (big, "usbgpu")
|
||||
assert resolve_bundle_by_ref("big", {"qcom": [], "chestnut": [big]}) == (big, "chestnut")
|
||||
|
||||
def test_unknown_ref_returns_none(self):
|
||||
source_bundles = {"qcom": [self._bundle("small")], "usbgpu": []}
|
||||
source_bundles = {"qcom": [self._bundle("small")], "chestnut": []}
|
||||
assert resolve_bundle_by_ref("nope", source_bundles) is None
|
||||
|
||||
|
||||
def manifest_bundle(short_name: str, ref: str, index: int = 0, is_big: bool = False) -> dict:
|
||||
"""Minimal manifest bundle dict, version-compatible (no chunks to avoid disk side effects).
|
||||
Big (usbgpu) bundles carry `is_big: true` in the manifest JSON."""
|
||||
Big (chestnut) bundles carry `is_big: true` in the manifest JSON."""
|
||||
return {
|
||||
"index": index,
|
||||
"short_name": short_name,
|
||||
@@ -489,15 +489,15 @@ class TestModelFetcherSources(OpenpilotTestCase):
|
||||
"""Both manifests are always maintained: get_bundles_for_source exposes either
|
||||
source by name, and active_source picks which one matches the attached hardware."""
|
||||
|
||||
def _make_params(self, qcom_manifest, usbgpu_manifest):
|
||||
def _make_params(self, qcom_manifest, chestnut_manifest):
|
||||
params = mock.MagicMock()
|
||||
|
||||
def get(key):
|
||||
if key == "ModelManager_ModelsCache":
|
||||
return qcom_manifest
|
||||
if key == "ModelManager_ModelsCache_USBGPU":
|
||||
return usbgpu_manifest
|
||||
if key in ("ModelManager_LastSyncTime", "ModelManager_LastSyncTime_USBGPU"):
|
||||
if key == "ModelManager_ModelsCache_Chestnut":
|
||||
return chestnut_manifest
|
||||
if key in ("ModelManager_LastSyncTime", "ModelManager_LastSyncTime_Chestnut"):
|
||||
return fresh_sync_time()
|
||||
return None
|
||||
|
||||
@@ -506,14 +506,14 @@ class TestModelFetcherSources(OpenpilotTestCase):
|
||||
|
||||
def test_active_source_follows_chestnut_presence(self):
|
||||
assert ModelFetcher.active_source(False) == "qcom"
|
||||
assert ModelFetcher.active_source(True) == "usbgpu"
|
||||
assert ModelFetcher.active_source(True) == "chestnut"
|
||||
|
||||
def test_get_bundles_for_source_returns_each_source(self):
|
||||
params = self._make_params({"bundles": [manifest_bundle("small", "aaa")]},
|
||||
{"bundles": [manifest_bundle("big", "bbb", is_big=True)]})
|
||||
fetcher = ModelFetcher(params)
|
||||
assert [bundle.ref for bundle in fetcher.get_bundles_for_source("qcom")] == ["aaa"]
|
||||
assert [bundle.ref for bundle in fetcher.get_bundles_for_source("usbgpu")] == ["bbb"]
|
||||
assert [bundle.ref for bundle in fetcher.get_bundles_for_source("chestnut")] == ["bbb"]
|
||||
|
||||
def test_get_bundles_for_source_unknown(self):
|
||||
assert ModelFetcher(mock.MagicMock()).get_bundles_for_source("bogus") == []
|
||||
@@ -522,16 +522,16 @@ class TestModelFetcherSources(OpenpilotTestCase):
|
||||
params = self._make_params({"bundles": [manifest_bundle("small", "aaa")]},
|
||||
{"bundles": [manifest_bundle("big", "bbb", is_big=True)]})
|
||||
qcom_bundles = get_cached_bundles(params, "qcom")
|
||||
usbgpu_bundles = get_cached_bundles(params, "usbgpu")
|
||||
chestnut_bundles = get_cached_bundles(params, "chestnut")
|
||||
assert [b.ref for b in qcom_bundles] == ["aaa"]
|
||||
assert [b.ref for b in usbgpu_bundles] == ["bbb"]
|
||||
assert [b.ref for b in chestnut_bundles] == ["bbb"]
|
||||
assert qcom_bundles[0].displayName == "SMALL"
|
||||
|
||||
def test_get_cached_bundles_empty_when_missing(self):
|
||||
params = mock.MagicMock()
|
||||
params.get.return_value = None
|
||||
assert get_cached_bundles(params, "qcom") == []
|
||||
assert get_cached_bundles(params, "usbgpu") == []
|
||||
assert get_cached_bundles(params, "chestnut") == []
|
||||
|
||||
def test_get_cached_bundles_unknown_source(self):
|
||||
assert get_cached_bundles(mock.MagicMock(), "bogus") == []
|
||||
@@ -543,27 +543,27 @@ class TestModelFetcherSources(OpenpilotTestCase):
|
||||
assert active_json_calls, "expected ModelManager_ActiveJson to be written"
|
||||
assert active_json_calls[-1].args[1] == {
|
||||
"qcom": ModelFetcher.MODEL_URL,
|
||||
"usbgpu": ModelFetcher.MODEL_URL_USBGPU,
|
||||
"chestnut": ModelFetcher.MODEL_URL_CHESTNUT,
|
||||
}
|
||||
|
||||
|
||||
|
||||
class TestSourceCacheIntegrity(OpenpilotTestCase):
|
||||
"""Each source's cached manifest must contain only that source's models; the
|
||||
`is_big` flag in the JSON marks the big (usbgpu) models. A mismatched cache is
|
||||
`is_big` flag in the JSON marks the big (chestnut) models. A mismatched cache is
|
||||
legacy data from before the per-source split (the active manifest was cached
|
||||
under the unsuffixed key regardless of hardware) and is refetched. This
|
||||
replaces the old one-time bundle migration."""
|
||||
|
||||
def _make_params(self, qcom_manifest, usbgpu_manifest):
|
||||
def _make_params(self, qcom_manifest, chestnut_manifest):
|
||||
params = mock.MagicMock()
|
||||
|
||||
def get(key):
|
||||
if key == "ModelManager_ModelsCache":
|
||||
return qcom_manifest
|
||||
if key == "ModelManager_ModelsCache_USBGPU":
|
||||
return usbgpu_manifest
|
||||
if key in ("ModelManager_LastSyncTime", "ModelManager_LastSyncTime_USBGPU"):
|
||||
if key == "ModelManager_ModelsCache_Chestnut":
|
||||
return chestnut_manifest
|
||||
if key in ("ModelManager_LastSyncTime", "ModelManager_LastSyncTime_Chestnut"):
|
||||
return fresh_sync_time()
|
||||
return None
|
||||
|
||||
@@ -584,13 +584,13 @@ class TestSourceCacheIntegrity(OpenpilotTestCase):
|
||||
bundles = fetcher.get_bundles_for_source("qcom")
|
||||
assert [bundle.ref for bundle in bundles] == ["aaa"]
|
||||
|
||||
def test_usbgpu_cache_without_big_models_is_refetched(self):
|
||||
def test_chestnut_cache_without_big_models_is_refetched(self):
|
||||
params = self._make_params({"bundles": [manifest_bundle("small", "aaa")]},
|
||||
{"bundles": [manifest_bundle("big2", "ccc")]})
|
||||
fetcher = ModelFetcher(params)
|
||||
fetched = self._fetched(manifest_bundle("big", "bbb", is_big=True))
|
||||
with mock.patch.object(fetcher, "_fetch_and_cache_models", return_value=fetched):
|
||||
bundles = fetcher.get_bundles_for_source("usbgpu")
|
||||
bundles = fetcher.get_bundles_for_source("chestnut")
|
||||
assert [bundle.ref for bundle in bundles] == ["bbb"]
|
||||
|
||||
def test_matching_caches_are_used_without_fetch(self):
|
||||
@@ -599,7 +599,7 @@ class TestSourceCacheIntegrity(OpenpilotTestCase):
|
||||
fetcher = ModelFetcher(params)
|
||||
with mock.patch.object(fetcher, "_fetch_and_cache_models", side_effect=AssertionError("cache should be used")):
|
||||
assert [bundle.ref for bundle in fetcher.get_bundles_for_source("qcom")] == ["aaa"]
|
||||
assert [bundle.ref for bundle in fetcher.get_bundles_for_source("usbgpu")] == ["bbb"]
|
||||
assert [bundle.ref for bundle in fetcher.get_bundles_for_source("chestnut")] == ["bbb"]
|
||||
|
||||
def test_stale_version_cache_is_refetched(self):
|
||||
"""A source-matching cache whose bundles are all filtered by the selector version
|
||||
@@ -660,11 +660,11 @@ class TestActiveBundleValidation(OpenpilotTestCase):
|
||||
bundle.runner = runner
|
||||
return bundle.to_dict()
|
||||
|
||||
def _params(self, qcom=None, usbgpu=None):
|
||||
def _params(self, qcom=None, chestnut=None):
|
||||
params = mock.MagicMock()
|
||||
|
||||
def get(key, *args, **kwargs):
|
||||
return {"ModelManager_ActiveBundle": qcom, "ModelManager_ActiveBundleUSBGPU": usbgpu}.get(key)
|
||||
return {"ModelManager_ActiveBundle": qcom, "ModelManager_ActiveBundleChestnut": chestnut}.get(key)
|
||||
|
||||
params.get.side_effect = get
|
||||
return params
|
||||
@@ -672,15 +672,15 @@ class TestActiveBundleValidation(OpenpilotTestCase):
|
||||
def test_empty_catalog_does_not_reset_slot(self):
|
||||
params = self._params(qcom=self._raw_bundle("small"))
|
||||
with mock.patch("openpilot.sunnypilot.models.helpers.chestnut_present", return_value=False):
|
||||
validate_active_bundles(params, {"qcom": [], "usbgpu": []})
|
||||
validate_active_bundles(params, {"qcom": [], "chestnut": []})
|
||||
params.remove.assert_not_called()
|
||||
|
||||
def test_reset_recomputes_runner_from_surviving_slot(self):
|
||||
tinygrad = int(custom.ModelManagerSP.Runner.tinygrad)
|
||||
big_raw = self._raw_bundle("big", runner=tinygrad)
|
||||
params = self._params(qcom=self._raw_bundle("gone"), usbgpu=big_raw)
|
||||
params = self._params(qcom=self._raw_bundle("gone"), chestnut=big_raw)
|
||||
catalog = {"qcom": [custom.ModelManagerSP.ModelBundle(**self._raw_bundle("other"))],
|
||||
"usbgpu": [custom.ModelManagerSP.ModelBundle(**big_raw)]}
|
||||
"chestnut": [custom.ModelManagerSP.ModelBundle(**big_raw)]}
|
||||
with mock.patch("openpilot.sunnypilot.models.helpers.chestnut_present", return_value=True):
|
||||
validate_active_bundles(params, catalog)
|
||||
params.remove.assert_called_once_with("ModelManager_ActiveBundle")
|
||||
@@ -689,7 +689,7 @@ class TestActiveBundleValidation(OpenpilotTestCase):
|
||||
|
||||
|
||||
class TestActiveBundleSelection(OpenpilotTestCase):
|
||||
"""The effective active bundle is the active source's slot: usbgpu when a GPU is
|
||||
"""The effective active bundle is the active source's slot: chestnut when a GPU is
|
||||
present, qcom otherwise. An empty active slot means the hardware default (stock
|
||||
runner), never the other slot's pick - modeld_v2 requires a real bundle."""
|
||||
|
||||
@@ -700,36 +700,36 @@ class TestActiveBundleSelection(OpenpilotTestCase):
|
||||
bundle.minimumSelectorVersion = 18
|
||||
return bundle.to_dict()
|
||||
|
||||
def _params(self, qcom=None, usbgpu=None):
|
||||
def _params(self, qcom=None, chestnut=None):
|
||||
params = mock.MagicMock()
|
||||
|
||||
def get(key, *args, **kwargs):
|
||||
if key == "ModelManager_ActiveBundle":
|
||||
return qcom
|
||||
if key == "ModelManager_ActiveBundleUSBGPU":
|
||||
return usbgpu
|
||||
if key == "ModelManager_ActiveBundleChestnut":
|
||||
return chestnut
|
||||
return None
|
||||
|
||||
params.get.side_effect = get
|
||||
return params
|
||||
|
||||
def test_selected_bundle_is_per_slot(self):
|
||||
params = self._params(qcom=self._raw_bundle("small"), usbgpu=self._raw_bundle("big"))
|
||||
params = self._params(qcom=self._raw_bundle("small"), chestnut=self._raw_bundle("big"))
|
||||
assert get_selected_bundle(params, "qcom").ref == "small"
|
||||
assert get_selected_bundle(params, "usbgpu").ref == "big"
|
||||
assert get_selected_bundle(params, "chestnut").ref == "big"
|
||||
|
||||
def test_no_gpu_uses_qcom_slot(self):
|
||||
params = self._params(qcom=self._raw_bundle("small"), usbgpu=self._raw_bundle("big"))
|
||||
params = self._params(qcom=self._raw_bundle("small"), chestnut=self._raw_bundle("big"))
|
||||
with mock.patch("openpilot.sunnypilot.models.helpers.chestnut_present", return_value=False):
|
||||
assert get_active_bundle(params).ref == "small"
|
||||
|
||||
def test_gpu_uses_usbgpu_slot(self):
|
||||
params = self._params(qcom=self._raw_bundle("small"), usbgpu=self._raw_bundle("big"))
|
||||
def test_gpu_uses_chestnut_slot(self):
|
||||
params = self._params(qcom=self._raw_bundle("small"), chestnut=self._raw_bundle("big"))
|
||||
with mock.patch("openpilot.sunnypilot.models.helpers.chestnut_present", return_value=True):
|
||||
assert get_active_bundle(params).ref == "big"
|
||||
|
||||
def test_gpu_without_big_selection_is_hardware_default(self):
|
||||
params = self._params(qcom=self._raw_bundle("small"), usbgpu=None)
|
||||
params = self._params(qcom=self._raw_bundle("small"), chestnut=None)
|
||||
with mock.patch("openpilot.sunnypilot.models.helpers.chestnut_present", return_value=True):
|
||||
assert get_active_bundle(params) is None
|
||||
|
||||
@@ -753,31 +753,31 @@ class TestEffectiveSource(OpenpilotTestCase):
|
||||
|
||||
def test_runtime_gpu_present(self):
|
||||
with mock.patch("openpilot.sunnypilot.models.helpers.chestnut_present", return_value=True):
|
||||
assert get_active_source() == "usbgpu"
|
||||
assert get_active_source() == "chestnut"
|
||||
|
||||
def test_display_offroad_gpu_present_shows_big(self):
|
||||
assert get_active_source(usbgpu=True, usbgpu_active=False, usbgpu_loading=False, offroad=True) == "usbgpu"
|
||||
assert get_active_source(chestnut=True, chestnut_active=False, chestnut_loading=False, offroad=True) == "chestnut"
|
||||
|
||||
def test_display_onroad_gpu_loading_shows_big(self):
|
||||
assert get_active_source(usbgpu=True, usbgpu_active=False, usbgpu_loading=True, offroad=False) == "usbgpu"
|
||||
assert get_active_source(chestnut=True, chestnut_active=False, chestnut_loading=True, offroad=False) == "chestnut"
|
||||
|
||||
def test_display_onroad_gpu_active_shows_big(self):
|
||||
assert get_active_source(usbgpu=True, usbgpu_active=True, usbgpu_loading=False, offroad=False) == "usbgpu"
|
||||
assert get_active_source(chestnut=True, chestnut_active=True, chestnut_loading=False, offroad=False) == "chestnut"
|
||||
|
||||
def test_display_onroad_gpu_idle_shows_small(self):
|
||||
assert get_active_source(usbgpu=True, usbgpu_active=False, usbgpu_loading=False, offroad=False) == "qcom"
|
||||
assert get_active_source(chestnut=True, chestnut_active=False, chestnut_loading=False, offroad=False) == "qcom"
|
||||
|
||||
def test_display_active_none_is_idle(self):
|
||||
assert get_active_source(usbgpu=True, usbgpu_active=None, usbgpu_loading=False, offroad=False) == "qcom"
|
||||
assert get_active_source(chestnut=True, chestnut_active=None, chestnut_loading=False, offroad=False) == "qcom"
|
||||
|
||||
def test_active_bundle_follows_source(self):
|
||||
params = mock.MagicMock()
|
||||
params.get.side_effect = lambda key: {"ModelManager_ActiveBundle": self._raw_bundle("small"),
|
||||
"ModelManager_ActiveBundleUSBGPU": self._raw_bundle("big")}.get(key)
|
||||
"ModelManager_ActiveBundleChestnut": self._raw_bundle("big")}.get(key)
|
||||
with mock.patch("openpilot.sunnypilot.models.helpers.chestnut_present", return_value=False):
|
||||
assert get_active_bundle(params).ref == "small"
|
||||
assert get_selected_bundle(params, get_active_source(usbgpu=True, usbgpu_active=False,
|
||||
usbgpu_loading=False, offroad=True)).ref == "big"
|
||||
assert get_selected_bundle(params, get_active_source(chestnut=True, chestnut_active=False,
|
||||
chestnut_loading=False, offroad=True)).ref == "big"
|
||||
|
||||
|
||||
@unittest.skipUnless(os.environ.get('RUN_INTEGRATION_TESTS'), 'requires external network')
|
||||
|
||||
@@ -65,7 +65,7 @@ def sp_stats(end_event):
|
||||
'MadsSteeringMode',
|
||||
'MadsUnifiedEngagementMode',
|
||||
'ModelManager_ActiveBundle',
|
||||
'ModelManager_ActiveBundleUSBGPU',
|
||||
'ModelManager_ActiveBundleChestnut',
|
||||
'ModelManager_Favs',
|
||||
'EnableSunnylinkUploader',
|
||||
'SunnylinkEnabled',
|
||||
|
||||
@@ -89,12 +89,16 @@ def _migrate_model_bundle_slots(_params):
|
||||
# ActiveBundle. Seed both slots; validation drops whichever does not match
|
||||
# its own manifest.
|
||||
try:
|
||||
if _params.get("ModelManager_ActiveBundleUSBGPU") is not None:
|
||||
if _params.get("ModelManager_ActiveBundleChestnut") is not None:
|
||||
return
|
||||
if (chestnut_bundle := _params.get("ModelManager_ActiveBundleUSBGPU")) is not None:
|
||||
_params.put("ModelManager_ActiveBundleChestnut", chestnut_bundle, block=True)
|
||||
cloudlog.info("params_migration: seeded ModelManager_ActiveBundleChestnut from ModelManager_ActiveBundleUSBGPU")
|
||||
return
|
||||
if (bundle := _params.get("ModelManager_ActiveBundle")) is None:
|
||||
return
|
||||
_params.put("ModelManager_ActiveBundleUSBGPU", bundle, block=True)
|
||||
cloudlog.info("params_migration: seeded ModelManager_ActiveBundleUSBGPU from ModelManager_ActiveBundle")
|
||||
_params.put("ModelManager_ActiveBundleChestnut", bundle, block=True)
|
||||
cloudlog.info("params_migration: seeded ModelManager_ActiveBundleChestnut from ModelManager_ActiveBundle")
|
||||
except Exception as e:
|
||||
cloudlog.exception(f"Error migrating model bundle slots: {e}")
|
||||
|
||||
@@ -136,5 +140,5 @@ def run_migration(_params):
|
||||
# seed TeslaMadsScreenButton for existing Tesla installs
|
||||
_migrate_tesla_mads_screen_button(_params)
|
||||
|
||||
# seed the usbgpu model slot from the pre-split single slot
|
||||
# seed the chestnut model slot from the pre-split single slot
|
||||
_migrate_model_bundle_slots(_params)
|
||||
|
||||
@@ -15,22 +15,22 @@ class TestModelBundleSlotMigration(OpenpilotTestCase):
|
||||
The migration seeds both slots; per-source validation later drops whichever does not
|
||||
match its own manifest."""
|
||||
|
||||
def test_seeds_usbgpu_slot_from_active_bundle(self):
|
||||
def test_seeds_chestnut_slot_from_active_bundle(self):
|
||||
params = Params()
|
||||
bundle = {"ref": "big", "minimumSelectorVersion": 18}
|
||||
params.put("ModelManager_ActiveBundle", bundle, block=True)
|
||||
_migrate_model_bundle_slots(params)
|
||||
assert params.get("ModelManager_ActiveBundleUSBGPU") == bundle
|
||||
assert params.get("ModelManager_ActiveBundleChestnut") == bundle
|
||||
assert params.get("ModelManager_ActiveBundle") == bundle
|
||||
|
||||
def test_noop_when_usbgpu_slot_already_set(self):
|
||||
def test_noop_when_chestnut_slot_already_set(self):
|
||||
params = Params()
|
||||
params.put("ModelManager_ActiveBundle", {"ref": "small"}, block=True)
|
||||
params.put("ModelManager_ActiveBundleUSBGPU", {"ref": "big"}, block=True)
|
||||
params.put("ModelManager_ActiveBundleChestnut", {"ref": "big"}, block=True)
|
||||
_migrate_model_bundle_slots(params)
|
||||
assert params.get("ModelManager_ActiveBundleUSBGPU") == {"ref": "big"}
|
||||
assert params.get("ModelManager_ActiveBundleChestnut") == {"ref": "big"}
|
||||
|
||||
def test_noop_when_no_selection(self):
|
||||
params = Params()
|
||||
_migrate_model_bundle_slots(params)
|
||||
assert params.get("ModelManager_ActiveBundleUSBGPU") is None
|
||||
assert params.get("ModelManager_ActiveBundleChestnut") is None
|
||||
|
||||
Reference in New Issue
Block a user