Add GPU model gating and v24 manifest support

This commit is contained in:
firestar5683
2026-08-15 17:39:46 -05:00
parent ddf95f3e45
commit d84a6a95c1
5 changed files with 75 additions and 7 deletions
@@ -19,7 +19,9 @@ from openpilot.starpilot.assets.model_manager import (
MODEL_DOWNLOAD_PARAM,
ModelManager,
canonical_model_key,
external_gpu_available,
is_builtin_model_key,
model_uses_external_gpu,
model_key_aliases,
)
from openpilot.starpilot.common.starpilot_variables import MODELS_PATH, update_starpilot_toggles
@@ -103,6 +105,7 @@ class ModelCatalogEntry:
partial: bool
community_favorite: bool
user_favorite: bool
requires_external_gpu: bool = False
def _clean_model_name(name: str) -> str:
@@ -637,6 +640,8 @@ class DrivingModelManagerView(AetherInteractiveMixin, Widget):
badge_parts.append(tr("Built-in"))
if entry.partial:
badge_parts.append(tr("Incomplete"))
if entry.requires_external_gpu:
badge_parts.append(tr("GPU required"))
if entry.user_favorite:
badge_parts.append(tr("Saved"))
elif entry.community_favorite:
@@ -963,6 +968,7 @@ class StarPilotDrivingModelLayout(_SettingsPage):
partial=partial,
community_favorite=(key in self._community_favorites),
user_favorite=(key in self._user_favorites),
requires_external_gpu=model_uses_external_gpu(key),
)
def _update_model_metadata(self):
@@ -1176,6 +1182,9 @@ class StarPilotDrivingModelLayout(_SettingsPage):
if entry is None or not entry.installed:
gui_app.push_widget(alert_dialog(tr("Model is not available on this device.")))
return False
if entry.requires_external_gpu and not external_gpu_available():
gui_app.push_widget(alert_dialog(tr("This model requires a detected external GPU.")))
return False
if self._params.get_bool("ModelRandomizer"):
gui_app.push_widget(alert_dialog(tr("Turn off Model Randomizer to choose a model manually.")))
return False
@@ -1209,6 +1218,9 @@ class StarPilotDrivingModelLayout(_SettingsPage):
if entry is None:
gui_app.push_widget(alert_dialog(tr("Unknown model.")))
return False
if entry.requires_external_gpu and not external_gpu_available():
gui_app.push_widget(alert_dialog(tr("This model requires a detected external GPU.")))
return False
if entry.installed:
gui_app.push_widget(alert_dialog(tr("Model is already on this device.")))
return False
@@ -9,6 +9,7 @@ from pathlib import Path
from openpilot.common.file_chunker import get_chunk_name, get_manifest_path
from openpilot.common.params import Params
from openpilot.starpilot.assets.model_manager import external_gpu_available, model_uses_external_gpu
from openpilot.selfdrive.ui.mici.widgets.button import BigButton
from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigDialogBase, BigMultiOptionDialog
from openpilot.selfdrive.ui.ui_state import ui_state
@@ -62,6 +63,7 @@ class ModelEntry:
version: str
released: str
community_favorite: bool
requires_external_gpu: bool = False
def _clean_model_name(name: str) -> str:
@@ -515,6 +517,10 @@ class DrivingModelBigButton(BigButton):
return entry_list
def _start_model_download(self, model_key: str):
entry = next((item for item in self._load_model_entries() if item.key == model_key), None)
if entry is not None and entry.requires_external_gpu and not external_gpu_available():
self._show_message("GPU required", "This model requires a detected external GPU.", return_to_manager=True)
return
if not self._start_worker("download", self._run_download_one, model_key):
self._show_message("Model manager busy", "Please wait for the current task.", return_to_manager=True)
return
@@ -546,6 +552,10 @@ class DrivingModelBigButton(BigButton):
self._show_message("Model unavailable", "Refresh manifest and try again.", return_to_manager=True)
return
if entry.requires_external_gpu and not external_gpu_available():
self._show_message("GPU required", "This model requires a detected external GPU.", return_to_manager=True)
return
if not self._is_model_installed(entry.key, entry.version):
self._show_message("Model not downloaded", "Download this model first.", return_to_manager=True)
return
@@ -662,6 +672,7 @@ class DrivingModelBigButton(BigButton):
version=version,
released=released,
community_favorite=(key in community_favs),
requires_external_gpu=model_uses_external_gpu(key),
))
return entries
+27 -1
View File
@@ -22,8 +22,9 @@ from openpilot.starpilot.common.model_versions import (
)
from openpilot.starpilot.common.starpilot_utilities import delete_file
from openpilot.starpilot.common.starpilot_variables import MODELS_PATH
from openpilot.system.hardware.usb import chestnut_firmware_ready
MANIFEST_CANDIDATES = ("v23",)
MANIFEST_CANDIDATES = ("v24",)
MODEL_NAMESPACE_SUFFIX = "3"
DEFAULT_MODEL_KEY = "rdf43"
LOCAL_MODEL_PREFIX = "local-"
@@ -100,6 +101,14 @@ def model_uses_external_gpu(model_key: str) -> bool:
return bool(load_model_artifact_metadata(model_key).get("uses_external_gpu", False))
def external_gpu_available() -> bool:
"""Return whether the supported external GPU link is ready for modeld."""
try:
return bool(chestnut_firmware_ready())
except Exception:
return False
class ModelManager:
def __init__(self, params, params_memory, boot_run=False):
self.params = params
@@ -377,6 +386,8 @@ class ModelManager:
canonical_key = self._canonical_model_key(model_key)
if canonical_key in blacklisted_keys or canonical_key in seen_keys:
continue
if model_uses_external_gpu(canonical_key) and not external_gpu_available():
continue
model_version = version_map.get(model_key) or version_map.get(canonical_key) or ""
if not model_version and is_builtin_model_key(canonical_key):
@@ -484,6 +495,13 @@ class ModelManager:
return
selected = self._selected_model()
if model_uses_external_gpu(selected) and not external_gpu_available():
default_name = self._default_param_text("DrivingModelName") or "Regret Driven Framework V4"
default_version = self._default_param_text("ModelVersion") or self._default_param_text("DrivingModelVersion") or "v15"
self._set_model_param_keys(DEFAULT_MODEL_KEY, default_name, default_version)
print(f"Model {selected} requires an external GPU; selected built-in model instead.")
return
if is_builtin_model_key(selected):
self._sync_selected_model_version()
return
@@ -673,6 +691,11 @@ class ModelManager:
self.downloading_model = False
return
if model_uses_external_gpu(model_to_download) and not external_gpu_available():
handle_error(None, "External GPU required...", "This model requires a detected external GPU.", MODEL_DOWNLOAD_PARAM, DOWNLOAD_PROGRESS_PARAM, self.params_memory)
self.downloading_model = False
return
# Local models have no upstream URL; a download attempt would 404 and then
# delete_file() the artifact on verification failure.
if is_local_model_key(model_to_download):
@@ -787,6 +810,9 @@ class ModelManager:
if is_local_model_key(model_key):
continue
if model_uses_external_gpu(model_key) and not external_gpu_available():
continue
artifact_format = artifact_format_map.get(model_key, "")
if self._is_model_downloaded(model_key, artifact_format):
continue
@@ -15,12 +15,12 @@ from openpilot.starpilot.assets.model_manager import MANIFEST_CANDIDATES, ModelM
from openpilot.starpilot.common.model_versions import UNIFIED_ARTIFACT_FORMAT
def test_v23_is_the_only_manifest_candidate():
assert MANIFEST_CANDIDATES == ("v23",)
def test_v24_is_the_only_manifest_candidate():
assert MANIFEST_CANDIDATES == ("v24",)
def test_v23_manifest_is_loaded_from_models_checkout():
assert ModelManager._manifest_paths("v23") == ("Models/model_names_v23.json",)
def test_v24_manifest_is_loaded_from_models_checkout():
assert ModelManager._manifest_paths("v24") == ("Models/model_names_v24.json",)
def test_old_manifest_ids_resolve_to_v23_namespace():
+21 -2
View File
@@ -48,7 +48,13 @@ from openpilot.system.version import get_build_metadata
from openpilot.tools.longitudinal_maneuvers.capabilities import get_longitudinal_maneuver_support
from panda import Panda
from openpilot.starpilot.assets.model_manager import canonical_model_key, is_builtin_model_key, model_key_aliases
from openpilot.starpilot.assets.model_manager import (
canonical_model_key,
external_gpu_available,
is_builtin_model_key,
model_key_aliases,
model_uses_external_gpu,
)
from openpilot.starpilot.assets.theme_manager import HOLIDAY_THEME_PATH, THEME_COMPONENT_PARAMS
from openpilot.starpilot.common.accel_profile import (
CUSTOM_ACCEL_PROFILE_INITIALIZED_KEY,
@@ -5008,6 +5014,8 @@ def setup(app):
selected_model = canonical_model_key(str_val.strip())
if not selected_model:
return jsonify({"error": "Driving model cannot be empty."}), 400
if model_uses_external_gpu(selected_model) and not external_gpu_available():
return jsonify({"error": "This model requires a detected external GPU."}), 409
params.put("Model", selected_model)
params.put("DrivingModel", selected_model)
@@ -5376,6 +5384,8 @@ def setup(app):
if model["installed"]:
return jsonify({"message": f"\"{model['label']}\" is already installed."}), 200
if model["requiresGpu"] and not model["gpuAvailable"]:
return jsonify({"error": "This model requires a detected external GPU."}), 409
params_memory.remove(MODEL_CANCEL_DOWNLOAD_PARAM)
params_memory.remove(MODEL_DOWNLOAD_ALL_PARAM)
@@ -5392,7 +5402,7 @@ def setup(app):
if params_memory.get_bool(MODEL_DOWNLOAD_ALL_PARAM) or (params_memory.get(MODEL_DOWNLOAD_PARAM, encoding="utf-8") or ""):
return jsonify({"error": "A model download is already in progress."}), 409
missing_models = [model for model in get_model_catalog() if not model["installed"]]
missing_models = [model for model in get_model_catalog() if not model["installed"] and (not model["requiresGpu"] or model["gpuAvailable"])]
if not missing_models:
return jsonify({"message": "All models are already installed."}), 200
@@ -5684,6 +5694,7 @@ def setup(app):
except Exception:
on_disk_files = set()
external_gpu_present = external_gpu_available()
models_by_key = {}
for i, key in enumerate(available):
canonical_key = canonical_model_key(key)
@@ -5695,6 +5706,8 @@ def setup(app):
artifact_format = artifact_formats[i] if i < len(artifact_formats) else ""
model_series = series[i] if i < len(series) and series[i] else "Custom Series"
released = released_dates[i] if i < len(released_dates) else ""
requires_external_gpu = model_uses_external_gpu(canonical_key)
gpu_available = not requires_external_gpu or external_gpu_present
existing = models_by_key.get(canonical_key)
if existing is None:
@@ -5704,6 +5717,8 @@ def setup(app):
"series": model_series,
"version": model_version,
"artifactFormat": artifact_format,
"requiresGpu": requires_external_gpu,
"gpuAvailable": gpu_available,
"released": released,
"builtin": is_builtin_model_key(canonical_key),
"communityFavorite": canonical_key in community_favorites,
@@ -5724,6 +5739,8 @@ def setup(app):
existing["builtin"] = existing["builtin"] or is_builtin_model_key(canonical_key)
existing["communityFavorite"] = existing["communityFavorite"] or canonical_key in community_favorites
existing["userFavorite"] = existing["userFavorite"] or canonical_key in user_favorites
existing["requiresGpu"] = existing["requiresGpu"] or requires_external_gpu
existing["gpuAvailable"] = not existing["requiresGpu"] or external_gpu_present
default_key = _default_model_key()
default_entry = models_by_key.setdefault(default_key, {
@@ -5732,6 +5749,8 @@ def setup(app):
"series": "Custom Series",
"version": _default_model_version(),
"artifactFormat": "tinygrad_single_v1",
"requiresGpu": False,
"gpuAvailable": True,
"released": "",
"builtin": True,
"communityFavorite": default_key in community_favorites,