allow smol when beeg

This commit is contained in:
firestar5683
2026-09-07 11:33:10 -05:00
parent 2ab6195d9b
commit 9ae473355a
9 changed files with 114 additions and 12 deletions
+12 -1
View File
@@ -37,6 +37,7 @@ ACTIVE_BIG_MODEL_VERSION_PARAM = "ActiveBigModelVersion"
ACTIVE_SMALL_MODEL_PARAM = "ActiveSmallModel"
ACTIVE_SMALL_MODEL_NAME_PARAM = "ActiveSmallModelName"
ACTIVE_SMALL_MODEL_VERSION_PARAM = "ActiveSmallModelVersion"
DISABLED_MODEL_PROFILE = "none"
MODEL_PROFILE_PARAMS = {
"big": (ACTIVE_BIG_MODEL_PARAM, ACTIVE_BIG_MODEL_NAME_PARAM, ACTIVE_BIG_MODEL_VERSION_PARAM),
"small": (ACTIVE_SMALL_MODEL_PARAM, ACTIVE_SMALL_MODEL_NAME_PARAM, ACTIVE_SMALL_MODEL_VERSION_PARAM),
@@ -154,7 +155,11 @@ def get_model_profile(params, profile: str) -> tuple[str, str, str]:
raise ValueError(f"Unknown model profile: {profile}")
key_param, name_param, version_param = MODEL_PROFILE_PARAMS[profile]
model_key = canonical_model_key(_params_text(params, key_param))
stored_value = _params_text(params, key_param)
if profile == "big" and stored_value.lower() == DISABLED_MODEL_PROFILE:
return "", "", ""
model_key = canonical_model_key(stored_value)
requires_gpu = profile == "big"
if model_key and model_uses_external_gpu(model_key) != requires_gpu:
model_key = ""
@@ -203,6 +208,12 @@ def set_model_profile(params, profile: str, model_key: str, model_name: str = ""
params.put(version_param, model_version or catalog_version or ("v15" if is_builtin_model_key(canonical_key) else ""))
def disable_big_model_profile(params) -> None:
params.put(ACTIVE_BIG_MODEL_PARAM, DISABLED_MODEL_PROFILE)
params.remove(ACTIVE_BIG_MODEL_NAME_PARAM)
params.remove(ACTIVE_BIG_MODEL_VERSION_PARAM)
def set_runtime_model_params(params, model_key: str, model_version: str = "") -> None:
canonical_key = canonical_model_key(model_key) or DEFAULT_MODEL_KEY
profile = "big" if model_uses_external_gpu(canonical_key) else "small"
@@ -177,6 +177,40 @@ def test_active_small_and_big_profiles_migrate_from_legacy_selection(tmp_path, m
assert model_manager.get_model_profile(big_params, "big") == ("big-one", "Big One", "v16")
def test_disabled_big_profile_does_not_migrate_from_legacy_selection(tmp_path, monkeypatch):
monkeypatch.setattr(model_manager, "MODELS_PATH", tmp_path)
(tmp_path / model_manager.ARTIFACT_METADATA_CACHE).write_text(json.dumps({
"big-one": {"uses_external_gpu": True},
}))
class FakeParams:
def __init__(self):
self.values = {
"Model": "big-one",
"DrivingModel": "big-one",
"ActiveBigModel": model_manager.DISABLED_MODEL_PROFILE,
"ActiveBigModelName": "Big One",
"ActiveBigModelVersion": "v16",
}
def get(self, key):
return self.values.get(key)
def put(self, key, value):
self.values[key] = value
def remove(self, key):
self.values.pop(key, None)
params = FakeParams()
assert model_manager.get_model_profile(params, "big") == ("", "", "")
model_manager.disable_big_model_profile(params)
assert params.values["ActiveBigModel"] == model_manager.DISABLED_MODEL_PROFILE
assert "ActiveBigModelName" not in params.values
assert "ActiveBigModelVersion" not in params.values
def test_runtime_model_metadata_does_not_overwrite_model_profiles(tmp_path, monkeypatch):
monkeypatch.setattr(model_manager, "MODELS_PATH", tmp_path)
(tmp_path / model_manager.ARTIFACT_METADATA_CACHE).write_text(json.dumps({
@@ -473,8 +473,8 @@ function bindDomHandlers() {
if (!(target instanceof HTMLSelectElement)) return;
if (target.id === "mm-active-small-model-select" || target.id === "mm-active-big-model-select") {
const modelKey = safeText(target.value, "");
if (!modelKey) return;
const profile = target.id === "mm-active-big-model-select" ? "big" : "small";
if (!modelKey && profile !== "big") return;
runAction(`select-${profile}`, modelKey).catch(() => {});
return;
}
@@ -669,7 +669,7 @@ export function ModelManager() {
</select>
<label class="mm-filter-label" for="mm-active-big-model-select">Active Big</label>
<select class="mm-select" id="mm-active-big-model-select" disabled="${() => getInstalledModels("big").length === 0}">
<select class="mm-select" id="mm-active-big-model-select">
${() => {
const orderedInstalled = getInstalledModels("big").sort((a, b) => {
const aCurrent = safeText(a.value) === state.activeBigModel ? 0 : 1;
@@ -680,14 +680,14 @@ export function ModelManager() {
return orderedInstalled.length > 0
? html`
${state.activeBigModel ? "" : html`<option value="" selected>Choose an eGPU model</option>`}
<option value="" selected="${() => !state.activeBigModel || false}">None — always use Active Small</option>
${orderedInstalled.map(model => html`
<option value="${safeText(model.value)}" selected="${() => safeText(model.value) === state.activeBigModel || false}">
${safeText(model.label, model.value)}
</option>
`)}
`
: html`<option value="">No installed eGPU models</option>`;
: html`<option value="" selected>None — always use Active Small</option>`;
}}
</select>
@@ -260,6 +260,7 @@ export const api = {
sentryPushSubscribe(body) { return request("/api/sentry/push/subscribe", { method: "POST", data: body }) },
getModelStatus() { return requestOk("/api/models/status", { cache: "no-store" }) },
setActiveModel(profile, modelKey = "") { return request("/api/models/active", { method: "PUT", data: { profile, model: modelKey } }) },
startModelDownload(modelKey, allowGpuWithoutGpu = false) { return request("/api/models/download", { method: "POST", data: { model: modelKey, allowGpuWithoutGpu } }) },
downloadAllModels(allowGpuWithoutGpu = false) { return request("/api/models/download_all", { method: "POST", data: { allowGpuWithoutGpu } }) },
deleteModel(modelKey) { return request("/api/models/delete", { method: "POST", data: { model: modelKey } }) },
@@ -24,6 +24,8 @@ export const ModelManager = {
allowGpu: false,
models: [],
currentModel: "",
activeSmallModel: "",
activeBigModel: "",
summary: { installed: 0, missing: 0, total: 0 },
status: {
modelToDownload: "",
@@ -41,6 +43,12 @@ export const ModelManager = {
const match = (this.models || []).find((m) => text(m && m.value, "") === key)
return match ? text(match.label, key) : key
},
installedSmallModels() {
return (this.models || []).filter((m) => m?.installed && !m?.requiresGpu)
},
installedBigModels() {
return (this.models || []).filter((m) => m?.installed && !!m?.requiresGpu)
},
sorted() {
const mode = this.sortMode
const rows = (this.models || [])
@@ -95,6 +103,8 @@ export const ModelManager = {
const p = await api.getModelStatus()
this.models = Array.isArray(p.models) ? p.models.filter((m) => m && typeof m === "object") : []
this.currentModel = text(p.currentModel, "")
this.activeSmallModel = text(p.activeSmallModel, "")
this.activeBigModel = text(p.activeBigModel, "")
const s = p.summary && typeof p.summary === "object" ? p.summary : {}
this.summary = {
installed: Number(s.installed) || 0,
@@ -129,8 +139,9 @@ export const ModelManager = {
this.busy = `${action}:${key}`
try {
let msg = ""
if (action === "select") {
const p = await api.updateParam({ key: "Model", value: key })
if (action === "select-small" || action === "select-big") {
const profile = action === "select-big" ? "big" : "small"
const p = await api.setActiveModel(profile, key)
msg = p?.message || `Selected "${label}".`
} else if (action === "download") {
const p = await api.startModelDownload(key, this.allowGpu)
@@ -222,6 +233,21 @@ export const ModelManager = {
<button type="button" class="gx-btn gx-btn--tonal" :disabled="!!busy" @click="runAction('refresh')"><i v-if="busy === 'refresh:'" class="bi bi-arrow-repeat gx-spin"></i><i v-else class="bi bi-arrow-clockwise"></i> Refresh</button>
</div>
<div class="gx-row" style="border-top:none;">
<span class="gx-row__label">Active Small</span>
<select class="gx-field" style="flex:1;" :value="activeSmallModel" :disabled="!!busy || status.isOnroad" @change="runAction('select-small', installedSmallModels.find(m => m.value === $event.target.value))">
<option v-for="m in installedSmallModels" :key="m.value" :value="m.value">{{ m.label || m.value }}</option>
</select>
</div>
<div class="gx-row" style="border-top:none;">
<span class="gx-row__label">Active Big</span>
<select class="gx-field" style="flex:1;" :value="activeBigModel" :disabled="!!busy || status.isOnroad" @change="$event.target.value ? runAction('select-big', installedBigModels.find(m => m.value === $event.target.value)) : runAction('select-big')">
<option value="">None — always use Active Small</option>
<option v-for="m in installedBigModels" :key="m.value" :value="m.value">{{ m.label || m.value }}</option>
</select>
</div>
<div class="gx-row" style="border-top:none;">
<span class="gx-row__label">Sort</span>
<select class="gx-field" style="flex:1;" :value="sortMode" @change="sortMode = $event.target.value">
@@ -294,7 +320,7 @@ export const ModelManager = {
<button type="button" class="gx-btn gx-btn--danger" :disabled="!!busy" @click="runAction('cancel', m)"><i class="bi bi-x-circle"></i> Cancel</button>
</template>
<template v-else-if="rowState(m) === 'installed'">
<button type="button" class="gx-btn" :disabled="!!busy" @click="runAction('select', m)"><i class="bi bi-play-fill"></i> Set Active</button>
<button type="button" class="gx-btn" :disabled="!!busy" @click="runAction(m.requiresGpu ? 'select-big' : 'select-small', m)"><i class="bi bi-play-fill"></i> Set Active {{ m.requiresGpu ? 'Big' : 'Small' }}</button>
<button v-if="!m.builtin" type="button" class="gx-btn gx-btn--tonal" style="color:var(--error);" :disabled="!!busy" @click="runAction('delete', m)"><i class="bi bi-trash"></i> Delete</button>
</template>
<template v-else>
@@ -44,9 +44,17 @@ model_manager = ModuleType("openpilot.starpilot.assets.model_manager")
model_manager.MODEL_LAB_DOWNLOAD_PARAM = "ModelLabModelToDownload"
model_manager.canonical_model_key = lambda value: str(value or "").strip().lower().replace(" ", "-")
model_manager.external_gpu_available = lambda: False
model_manager.disable_big_model_profile = lambda params: (
params.put("ActiveBigModel", "none"),
params.remove("ActiveBigModelName"),
params.remove("ActiveBigModelVersion"),
)
def _stub_get_model_profile(params, profile):
prefix = "ActiveBigModel" if profile == "big" else "ActiveSmallModel"
key = params.get(prefix) or ("rdf43" if profile == "small" else "")
stored_key = params.get(prefix)
if profile == "big" and stored_key == "none":
return "", "", ""
key = stored_key or ("rdf43" if profile == "small" else "")
return key, params.get(f"{prefix}Name") or key, params.get(f"{prefix}Version") or ""
@@ -1851,6 +1859,12 @@ def test_model_profiles_can_be_selected_without_external_gpu(monkeypatch, tmp_pa
assert status["activeBigModel"] == "big-one"
assert status["activeSmallModel"] == "small-one"
disabled = client.put("/api/models/active", json={"profile": "big", "model": ""})
assert disabled.status_code == 200
assert params.values["ActiveBigModel"] == "none"
assert disabled.get_json()["model"] == ""
assert client.get("/api/models/status").get_json()["activeBigModel"] == ""
wrong_profile = client.put("/api/models/active", json={"profile": "small", "model": "big-one"})
assert wrong_profile.status_code == 409
@@ -425,7 +425,7 @@ def test_ui_all_remaining_classic_tools_native_no_embed():
# Shared API surface added for the second batch of ported pages.
for method in ["selectTestingGround",
"getSentryStatus", "getSentryEvents", "deleteSentryEvent", "sentryPushSubscribe",
"getModelStatus", "startModelDownload", "downloadAllModels", "deleteModel", "saveModelPreferences",
"getModelStatus", "setActiveModel", "startModelDownload", "downloadAllModels", "deleteModel", "saveModelPreferences",
"getPlotsLive",
"getGalaxySession", "deleteNavigationKey",
"getThemeList", "saveTheme", "applyTheme", "deleteTheme", "downloadTheme",
+16 -1
View File
@@ -55,6 +55,7 @@ from panda import Panda
from openpilot.starpilot.assets.model_manager import (
MODEL_LAB_DOWNLOAD_PARAM,
canonical_model_key,
disable_big_model_profile,
external_gpu_available,
get_model_profile,
is_builtin_model_key,
@@ -6450,7 +6451,21 @@ def setup(app):
model_key = canonical_model_key(str(data.get("model") or "").strip())
if not model_key:
return jsonify({"error": "Missing model key."}), 400
if profile != "big":
return jsonify({"error": "Active Small cannot be disabled."}), 400
lab_config = normalize_model_lab_config(params.get(MODEL_LAB_CONFIG_PARAM, encoding="utf-8") or "")
if lab_config["enabled"]:
lab_config["enabled"] = False
params.put(MODEL_LAB_CONFIG_PARAM, lab_config)
params.remove(MODEL_LAB_RUNTIME_PARAM)
disable_big_model_profile(params)
return jsonify({
"message": "Active Big disabled. Active Small will be used even when Chestnut is connected.",
"profile": profile,
"model": "",
}), 200
catalog = {model["value"]: model for model in get_model_catalog()}
model = catalog.get(model_key)
+2 -1
View File
@@ -428,7 +428,8 @@ def hardware_thread(end_event, hw_queue) -> None:
if chestnut is not None:
chestnut.update(started_ts is None, last_hw_state.usb_state)
model_lab_config = params.get("ModelLabConfig")
chestnut_expected = bool(params.get("ActiveBigModel")) or (
active_big_model = params.get("ActiveBigModel", encoding="utf-8") or ""
chestnut_expected = active_big_model.lower() not in ("", "none") or (
isinstance(model_lab_config, dict) and bool(model_lab_config.get("enabled"))
)
chestnut_state = sm["chestnutState"]