mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-09-08 00:53:48 +08:00
allow smol when beeg
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"]
|
||||
|
||||
Reference in New Issue
Block a user