Studio: enforce 60s minimum on idle auto-unload TTL (0 stays off) (#7185)

* Studio: enforce 60s minimum on idle auto-unload TTL (0 stays off)

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Drop decorative section separator from idle TTL floor tests

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Daniel Han <danielhanchen@gmail.com>
Co-authored-by: danielhanchen <unslothshared@gmail.com>
This commit is contained in:
Nilay 2026-07-19 13:04:56 +05:30 committed by GitHub
commit e9ef2ac60f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 125 additions and 32 deletions

View file

@ -1609,11 +1609,11 @@ def test_env_idle_ttl_standalone_when_no_stored_value(monkeypatch):
def test_stored_idle_value_overrides_env_and_stays_gated(monkeypatch):
# An explicit stored value wins over the env default and remains gated on the
# auto-switch toggle.
store = {settings.AUTO_UNLOAD_IDLE_SETTING_KEY: 30}
store = {settings.AUTO_UNLOAD_IDLE_SETTING_KEY: 90}
monkeypatch.setattr(settings, "_cached_setting", lambda k, d = None: store.get(k, d))
monkeypatch.setenv("UNSLOTH_MODEL_IDLE_TTL", "600")
monkeypatch.setattr(settings, "get_openai_auto_switch_enabled", lambda: True)
assert settings.get_auto_unload_idle_seconds() == 30 # stored wins, not env
assert settings.get_auto_unload_idle_seconds() == 90 # stored wins, not env
monkeypatch.setattr(settings, "get_openai_auto_switch_enabled", lambda: False)
assert settings.get_auto_unload_idle_seconds() == 0 # explicit value still gated off
@ -3094,3 +3094,53 @@ def test_responses_stream_hint_matches_toggle_regardless_of_active_model(monkeyp
monkeypatch, enabled = False, active_model_name = "unsloth/Llama-3.2-1B-Instruct"
)
assert "Model auto-switch" in non_gguf_loaded
def test_setter_rejects_idle_below_floor(monkeypatch):
import storage.studio_db as db
writes = []
monkeypatch.setattr(db, "upsert_app_settings", lambda m: writes.append(dict(m)))
settings._cache.clear()
with pytest.raises(ValueError, match = "at least 60"):
settings.set_openai_auto_switch(True, 30)
assert writes == [] # rejected before any persist
# 0 (off) and >= 60 pass through unchanged.
assert settings.set_openai_auto_switch(True, 0)[1] == 0
assert settings.set_openai_auto_switch(True, 60)[1] == 60
assert settings.set_openai_auto_switch(True, 3600)[1] == 3600
def test_put_route_rejects_idle_below_floor():
import routes.settings as settings_route
from fastapi import HTTPException
payload = settings_route.OpenAIAutoSwitchPayload(enabled = True, auto_unload_idle_seconds = 30)
with pytest.raises(HTTPException) as excinfo:
settings_route.update_openai_auto_switch(payload, "tester")
assert excinfo.value.status_code == 400
def test_stored_legacy_idle_below_floor_is_clamped(monkeypatch):
# Values persisted before the floor existed are raised to it on read, for
# both the effective TTL and the value the settings UI displays.
store = {settings.AUTO_UNLOAD_IDLE_SETTING_KEY: 5}
monkeypatch.setattr(settings, "_cached_setting", lambda k, d = None: store.get(k, d))
monkeypatch.setattr(settings, "get_openai_auto_switch_enabled", lambda: True)
assert settings.get_auto_unload_idle_seconds() == 60
assert settings.get_stored_auto_unload_idle_seconds() == 60
store[settings.AUTO_UNLOAD_IDLE_SETTING_KEY] = 90
assert settings.get_auto_unload_idle_seconds() == 90
def test_env_idle_below_floor_is_clamped(monkeypatch):
monkeypatch.setattr(settings, "_cached_setting", lambda k, d = None: d)
monkeypatch.setenv(settings.MODEL_IDLE_TTL_ENV_VAR, "5")
assert settings.get_auto_unload_idle_seconds() == 60
monkeypatch.setenv(settings.MODEL_IDLE_TTL_ENV_VAR, "0")
assert settings.get_auto_unload_idle_seconds() == 0
monkeypatch.setenv(settings.MODEL_IDLE_TTL_ENV_VAR, "600")
assert settings.get_auto_unload_idle_seconds() == 600
monkeypatch.delenv(settings.MODEL_IDLE_TTL_ENV_VAR)
assert settings.get_auto_unload_idle_seconds() == 0

View file

@ -8,7 +8,9 @@ Two settings, both off by default so existing API behavior is unchanged:
names a downloaded local GGUF different from the loaded one transparently
loads it before serving (llama-swap-style). Unknown names pass through.
- ``openai_api_auto_unload_idle_seconds``: when > 0, the loaded GGUF is
unloaded after this many idle seconds to free VRAM.
unloaded after this many idle seconds to free VRAM. Enabled values have a
60s floor (0 stays "off"): a tiny TTL tears the model down between turns of
an active chat, forcing a full weight reload + prompt re-prefill per turn.
The idle TTL can also be set at startup via the ``UNSLOTH_MODEL_IDLE_TTL`` env
var. Unlike the stored setting (which stays gated on auto-switch), the env value
@ -33,6 +35,7 @@ MODEL_IDLE_TTL_ENV_VAR = "UNSLOTH_MODEL_IDLE_TTL"
DEFAULT_OPENAI_AUTO_SWITCH_ENABLED = False
DEFAULT_AUTO_UNLOAD_IDLE_SECONDS = 0
MIN_AUTO_UNLOAD_IDLE_SECONDS = 60
_CACHE_TTL_S = 2.0
_cache_lock = threading.Lock()
@ -58,6 +61,10 @@ def _coerce_int(value: Any) -> int | None:
return None
def _apply_idle_floor(seconds: int) -> int:
return 0 if seconds <= 0 else max(MIN_AUTO_UNLOAD_IDLE_SECONDS, seconds)
def _cached_setting(key: str, default: Any) -> Any:
"""Read an app setting, memoized for _CACHE_TTL_S to spare the hot path."""
now = time.monotonic()
@ -91,12 +98,34 @@ def _stored_idle_seconds() -> Optional[int]:
return _coerce_int(_cached_setting(AUTO_UNLOAD_IDLE_SETTING_KEY, None))
_env_floor_warned = False
def _env_idle_seconds() -> Optional[int]:
"""UNSLOTH_MODEL_IDLE_TTL as a non-negative seconds value, or None if unset/invalid."""
"""UNSLOTH_MODEL_IDLE_TTL as a non-negative seconds value, or None if unset/invalid.
Floored to MIN_AUTO_UNLOAD_IDLE_SECONDS here (with a one-time warning) since
headless/container deploys have no UI to surface a validation error."""
raw = os.environ.get(MODEL_IDLE_TTL_ENV_VAR)
if raw is None or not raw.strip():
return None
return _coerce_int(raw)
parsed = _coerce_int(raw)
if parsed is None:
return None
floored = _apply_idle_floor(parsed)
if floored != parsed:
global _env_floor_warned
if not _env_floor_warned:
_env_floor_warned = True
from loggers import get_logger
get_logger(__name__).warning(
"%s=%s is below the %ss minimum; using %ss",
MODEL_IDLE_TTL_ENV_VAR,
parsed,
MIN_AUTO_UNLOAD_IDLE_SECONDS,
floored,
)
return floored
def get_stored_auto_unload_idle_seconds() -> int:
@ -108,7 +137,9 @@ def get_stored_auto_unload_idle_seconds() -> int:
"""
stored = _stored_idle_seconds()
if stored is not None:
return stored
# Floor legacy values persisted before the minimum existed, so the UI
# displays the effective TTL and round-trips it cleanly.
return _apply_idle_floor(stored)
env = _env_idle_seconds()
return env if env is not None else DEFAULT_AUTO_UNLOAD_IDLE_SECONDS
@ -118,8 +149,9 @@ def get_auto_unload_idle_seconds() -> int:
stored = _stored_idle_seconds()
if stored is not None:
# An explicit UI/API value stays gated on auto-switch: off reports 0 so the
# off state is identical to pre-feature.
return stored if get_openai_auto_switch_enabled() else 0
# off state is identical to pre-feature. Floored to cover values persisted
# before the minimum existed.
return _apply_idle_floor(stored) if get_openai_auto_switch_enabled() else 0
# No stored value: UNSLOTH_MODEL_IDLE_TTL is a standalone startup default that
# enables idle-unload even with auto-switch off (headless/container deploys).
env = _env_idle_seconds()
@ -136,6 +168,11 @@ def set_openai_auto_switch(enabled: Any, idle_seconds: Any) -> tuple[bool, int]:
parsed_idle = _coerce_int(idle_seconds)
if parsed_idle is None:
raise ValueError("Auto-unload idle seconds must be a non-negative integer.")
if 0 < parsed_idle < MIN_AUTO_UNLOAD_IDLE_SECONDS:
raise ValueError(
f"Auto-unload idle seconds must be 0 (off) or at least "
f"{MIN_AUTO_UNLOAD_IDLE_SECONDS}."
)
from storage.studio_db import upsert_app_settings
upsert_app_settings(

View file

@ -14,6 +14,9 @@ import {
import { SettingsRow } from "./settings-row";
import { SettingsSection } from "./settings-section";
// Mirrors MIN_AUTO_UNLOAD_IDLE_SECONDS in the backend settings store.
const MIN_IDLE_SECONDS = 60;
export function ModelAutoSwitchSection() {
const t = useT();
const [settings, setSettings] = useState<OpenAIAutoSwitchSettings | null>(
@ -45,13 +48,16 @@ export function ModelAutoSwitchSection() {
};
}, [t]);
// Parse the idle-seconds draft to a non-negative integer; empty/invalid -> null.
// Parse the idle-seconds draft: 0 (off) or >= MIN_IDLE_SECONDS; else null.
const parseIdleSeconds = (): number | null => {
if (!draftIdleSeconds.trim()) {
return null;
}
const parsed = Number(draftIdleSeconds);
return Number.isInteger(parsed) && parsed >= 0 ? parsed : null;
if (!Number.isInteger(parsed)) {
return null;
}
return parsed === 0 || parsed >= MIN_IDLE_SECONDS ? parsed : null;
};
const persist = async (

View file

@ -155,14 +155,14 @@ export const ar = {
"عندما يسمّي طلب متوافق مع OpenAI ملف GGUF مُنزّلاً مختلفًا، يتم تحميله قبل الخدمة. مُعطّل افتراضيًا؛ الأسماء غير المعروفة تُبقي على النموذج المُحمَّل.",
idleUnload: "الإلغاء التلقائي عند الخمول",
idleUnloadDescription:
"إلغاء تحميل النموذج بعد هذا العدد من ثواني الخمول لتحرير الـ VRAM؛ الطلب التالي يعيد تحميله. القيمة 0 تُبقيه محمَّلاً.",
"إلغاء تحميل النموذج بعد هذا العدد من ثواني الخمول لتحرير الـ VRAM؛ الطلب التالي يعيد تحميله. القيمة 0 تُبقيه محمَّلاً. الحد الأدنى 60 ثانية.",
idleNeedsEnable:
"فعّل تبديل النموذج حسب الطلب حتى يعاد تحميل النموذج غير المحمَّل عند الاستخدام التالي.",
idleActiveViaEnv:
"الإلغاء التلقائي عند الخمول مُفعَّل عبر متغير البيئة UNSLOTH_MODEL_IDLE_TTL.",
loadError: "فشل تحميل إعدادات التبديل التلقائي للنموذج.",
saveError: "فشل حفظ إعدادات التبديل التلقائي للنموذج.",
idleError: "أدخل عددًا صحيحًا من الثواني (0 أو أكثر).",
idleError: "أدخل 0 لإبقاء النموذج محمَّلاً، أو 60 ثانية على الأقل.",
},
previewSharing: {
sectionTitle: "مشاركة المعاينة",

View file

@ -158,7 +158,7 @@ export const de = {
"Wenn eine OpenAI-kompatible Anfrage ein anderes heruntergeladenes GGUF nennt, wird dieses vor der Auslieferung geladen. Standardmäßig aus; unbekannte Namen liefern weiterhin das geladene Modell aus.",
idleUnload: "Automatisches Entladen bei Inaktivität",
idleUnloadDescription:
"Entlädt das Modell nach dieser Anzahl inaktiver Sekunden, um VRAM freizugeben; die nächste Anfrage lädt es erneut. 0 hält es geladen.",
"Entlädt das Modell nach dieser Anzahl inaktiver Sekunden, um VRAM freizugeben; die nächste Anfrage lädt es erneut. 0 hält es geladen. Minimum 60 Sekunden.",
idleNeedsEnable:
"Aktivieren Sie \"Modell je Anfrage wechseln\", damit ein entladenes Modell bei der nächsten Nutzung erneut geladen wird.",
idleActiveViaEnv:
@ -167,7 +167,7 @@ export const de = {
"Einstellungen für automatischen Modellwechsel konnten nicht geladen werden.",
saveError:
"Einstellungen für automatischen Modellwechsel konnten nicht gespeichert werden.",
idleError: "Geben Sie eine ganze Anzahl an Sekunden ein (0 oder mehr).",
idleError: "Geben Sie 0 ein, um das Modell geladen zu halten, oder mindestens 60 Sekunden.",
},
previewSharing: {
sectionTitle: "Vorschau-Freigabe",

View file

@ -224,14 +224,14 @@ export const en = {
"When an OpenAI-compatible request names a different downloaded GGUF, load it before serving. Off by default; unknown names keep serving the loaded model.",
idleUnload: "Idle auto-unload",
idleUnloadDescription:
"Unload the model after this many idle seconds to free VRAM; the next request reloads it. 0 keeps it loaded.",
"Unload the model after this many idle seconds to free VRAM; the next request reloads it. 0 keeps it loaded. Minimum 60 seconds.",
idleNeedsEnable:
"Turn on Switch model by request so an unloaded model reloads on next use.",
idleActiveViaEnv:
"Idle auto-unload is active via the UNSLOTH_MODEL_IDLE_TTL environment variable.",
loadError: "Failed to load model auto-switch settings.",
saveError: "Failed to save model auto-switch settings.",
idleError: "Enter a whole number of seconds (0 or more).",
idleError: "Enter 0 to keep the model loaded, or at least 60 seconds.",
},
previewSharing: {
sectionTitle: "Preview sharing",

View file

@ -157,7 +157,7 @@ export const es = {
"Cuando una solicitud compatible con OpenAI nombra un GGUF descargado distinto, se carga antes de responder. Desactivado por defecto; los nombres desconocidos siguen usando el modelo cargado.",
idleUnload: "Descarga automática por inactividad",
idleUnloadDescription:
"Descarga el modelo tras este número de segundos inactivo para liberar VRAM; la siguiente solicitud lo recarga. 0 lo mantiene cargado.",
"Descarga el modelo tras este número de segundos inactivo para liberar VRAM; la siguiente solicitud lo recarga. 0 lo mantiene cargado. Mínimo 60 segundos.",
idleNeedsEnable:
"Activa Cambiar de modelo según la solicitud para que un modelo descargado se recargue en el próximo uso.",
idleActiveViaEnv:
@ -166,7 +166,7 @@ export const es = {
"No se pudo cargar la configuración de cambio automático de modelo.",
saveError:
"No se pudo guardar la configuración de cambio automático de modelo.",
idleError: "Introduce un número entero de segundos (0 o más).",
idleError: "Introduce 0 para mantener el modelo cargado, o al menos 60 segundos.",
},
previewSharing: {
sectionTitle: "Compartir vista previa",

View file

@ -157,7 +157,7 @@ export const fr = {
"Lorsqu'une requête compatible OpenAI nomme un autre GGUF téléchargé, le charger avant de répondre. Désactivé par défaut ; les noms inconnus continuent de servir le modèle chargé.",
idleUnload: "Déchargement automatique en cas d'inactivité",
idleUnloadDescription:
"Décharger le modèle après ce nombre de secondes d'inactivité pour libérer la VRAM ; la requête suivante le recharge. 0 le maintient chargé.",
"Décharger le modèle après ce nombre de secondes d'inactivité pour libérer la VRAM ; la requête suivante le recharge. 0 le maintient chargé. Minimum 60 secondes.",
idleNeedsEnable:
"Activez Changer de modèle par requête pour qu'un modèle déchargé se recharge à la prochaine utilisation.",
idleActiveViaEnv:
@ -166,7 +166,7 @@ export const fr = {
"Échec du chargement des paramètres de changement automatique de modèle.",
saveError:
"Échec de l'enregistrement des paramètres de changement automatique de modèle.",
idleError: "Saisissez un nombre entier de secondes (0 ou plus).",
idleError: "Saisissez 0 pour garder le modèle chargé, ou au moins 60 secondes.",
},
previewSharing: {
sectionTitle: "Partage de l'aperçu",

View file

@ -154,14 +154,14 @@ export const hi = {
"जब कोई OpenAI-संगत अनुरोध किसी अन्य डाउनलोड किए गए GGUF का नाम लेता है, तो सर्व करने से पहले उसे लोड करें। डिफ़ॉल्ट रूप से बंद; अज्ञात नाम लोड किए गए मॉडल को सर्व करते रहते हैं।",
idleUnload: "निष्क्रिय ऑटो-अनलोड",
idleUnloadDescription:
"VRAM मुक्त करने के लिए इतने निष्क्रिय सेकंड के बाद मॉडल को अनलोड करें; अगला अनुरोध इसे फिर से लोड करता है। 0 इसे लोड रखता है।",
"VRAM मुक्त करने के लिए इतने निष्क्रिय सेकंड के बाद मॉडल को अनलोड करें; अगला अनुरोध इसे फिर से लोड करता है। 0 इसे लोड रखता है। न्यूनतम 60 सेकंड।",
idleNeedsEnable:
"अनुरोध के अनुसार मॉडल बदलें चालू करें ताकि अनलोड किया गया मॉडल अगले उपयोग पर फिर से लोड हो।",
idleActiveViaEnv:
"निष्क्रिय ऑटो-अनलोड UNSLOTH_MODEL_IDLE_TTL एनवायरनमेंट वेरिएबल के माध्यम से सक्रिय है।",
loadError: "मॉडल ऑटो-स्विच सेटिंग्स लोड करने में विफल।",
saveError: "मॉडल ऑटो-स्विच सेटिंग्स सहेजने में विफल।",
idleError: "सेकंड की पूरी संख्या दर्ज करें (0 या अधिक)।",
idleError: "मॉडल को लोड रखने के लिए 0 दर्ज करें, या कम से कम 60 सेकंड।",
},
previewSharing: {
sectionTitle: "पूर्वावलोकन साझाकरण",

View file

@ -149,12 +149,12 @@ export const ja = {
enable: "リクエストごとにモデルを切り替え",
enableDescription: "OpenAI互換のリクエストが別のダウンロード済み GGUF を指定した場合、応答する前にそのモデルを読み込みます。デフォルトはオフです。不明な名前の場合は、読み込み済みのモデルで応答を続けます。",
idleUnload: "アイドル時の自動アンロード",
idleUnloadDescription: "指定した秒数だけアイドル状態が続くとモデルをアンロードして VRAM を解放します。次のリクエストで再読み込みされます。0 にすると読み込んだままにします。",
idleUnloadDescription: "指定した秒数だけアイドル状態が続くとモデルをアンロードして VRAM を解放します。次のリクエストで再読み込みされます。0 にすると読み込んだままにします。最小 60 秒。",
idleNeedsEnable: "アンロードされたモデルが次回使用時に再読み込みされるように、「リクエストごとにモデルを切り替え」をオンにしてください。",
idleActiveViaEnv: "アイドル時の自動アンロードは UNSLOTH_MODEL_IDLE_TTL 環境変数によって有効になっています。",
loadError: "モデル自動切り替え設定の読み込みに失敗しました。",
saveError: "モデル自動切り替え設定の保存に失敗しました。",
idleError: "秒数を整数0 以上)で入力してください。",
idleError: "モデルを読み込んだままにするには 0 を、それ以外は 60 秒以上を入力してください。",
},
previewSharing: {
sectionTitle: "プレビュー共有",

View file

@ -153,14 +153,14 @@ export const ko = {
"OpenAI 호환 요청이 다운로드된 다른 GGUF를 지정하면, 응답하기 전에 해당 모델을 불러옵니다. 기본값은 꺼짐이며, 알 수 없는 이름은 불러온 모델을 계속 제공합니다.",
idleUnload: "유휴 시 자동 해제",
idleUnloadDescription:
"지정한 유휴 시간(초)이 지나면 모델을 해제하여 VRAM을 확보합니다. 다음 요청 시 다시 불러옵니다. 0으로 설정하면 계속 로드된 상태로 유지됩니다.",
"지정한 유휴 시간(초)이 지나면 모델을 해제하여 VRAM을 확보합니다. 다음 요청 시 다시 불러옵니다. 0으로 설정하면 계속 로드된 상태로 유지됩니다. 최소 60초입니다.",
idleNeedsEnable:
"해제된 모델이 다음 사용 시 다시 로드되도록 하려면 요청에 따라 모델 전환을 켜세요.",
idleActiveViaEnv:
"유휴 시 자동 해제가 UNSLOTH_MODEL_IDLE_TTL 환경 변수를 통해 활성화되어 있습니다.",
loadError: "모델 자동 전환 설정을 불러오지 못했습니다.",
saveError: "모델 자동 전환 설정을 저장하지 못했습니다.",
idleError: "정수(초)를 입력하세요(0 이상).",
idleError: "모델을 로드 상태로 유지하려면 0을, 그렇지 않으면 60초 이상을 입력하세요.",
},
previewSharing: {
sectionTitle: "미리보기 공유",

View file

@ -157,14 +157,14 @@ export const ptBR = {
"Quando uma requisição compatível com OpenAI nomear um GGUF baixado diferente, carrega-o antes de responder. Desativado por padrão; nomes desconhecidos continuam usando o modelo carregado.",
idleUnload: "Descarregamento automático por inatividade",
idleUnloadDescription:
"Descarrega o modelo após esta quantidade de segundos de inatividade para liberar VRAM; a próxima requisição o recarrega. 0 mantém o modelo carregado.",
"Descarrega o modelo após esta quantidade de segundos de inatividade para liberar VRAM; a próxima requisição o recarrega. 0 mantém o modelo carregado. Mínimo de 60 segundos.",
idleNeedsEnable:
"Ative Trocar de modelo por requisição para que um modelo descarregado seja recarregado no próximo uso.",
idleActiveViaEnv:
"O descarregamento automático por inatividade está ativo por meio da variável de ambiente UNSLOTH_MODEL_IDLE_TTL.",
loadError: "Falha ao carregar as configurações de troca automática de modelo.",
saveError: "Falha ao salvar as configurações de troca automática de modelo.",
idleError: "Insira um número inteiro de segundos (0 ou mais).",
idleError: "Insira 0 para manter o modelo carregado, ou pelo menos 60 segundos.",
},
previewSharing: {
sectionTitle: "Compartilhamento de pré-visualização",

View file

@ -154,14 +154,14 @@ export const ru = {
"Когда OpenAI-совместимый запрос указывает другую загруженную GGUF, загружать её перед обслуживанием. По умолчанию выключено; неизвестные имена продолжают обслуживать загруженную модель.",
idleUnload: "Автовыгрузка при простое",
idleUnloadDescription:
"Выгружать модель после указанного числа секунд простоя, чтобы освободить VRAM; следующий запрос загрузит её снова. 0 оставляет модель загруженной.",
"Выгружать модель после указанного числа секунд простоя, чтобы освободить VRAM; следующий запрос загрузит её снова. 0 оставляет модель загруженной. Минимум 60 секунд.",
idleNeedsEnable:
"Включите «Переключать модель по запросу», чтобы выгруженная модель загружалась при следующем использовании.",
idleActiveViaEnv:
"Автовыгрузка при простое активна через переменную окружения UNSLOTH_MODEL_IDLE_TTL.",
loadError: "Не удалось загрузить настройки автопереключения модели.",
saveError: "Не удалось сохранить настройки автопереключения модели.",
idleError: "Введите целое число секунд (0 или больше).",
idleError: "Введите 0, чтобы модель оставалась загруженной, или не менее 60 секунд.",
},
previewSharing: {
sectionTitle: "Публикация предпросмотра",

View file

@ -152,14 +152,14 @@ export const zhCN = {
"当兼容 OpenAI 的请求指定了另一个已下载的 GGUF 时,先加载它再提供服务。默认关闭;未知名称将继续使用已加载的模型。",
idleUnload: "空闲自动卸载",
idleUnloadDescription:
"空闲达到该秒数后卸载模型以释放 VRAM下次请求会重新加载。设为 0 则保持加载。",
"空闲达到该秒数后卸载模型以释放 VRAM下次请求会重新加载。设为 0 则保持加载。最小 60 秒。",
idleNeedsEnable:
"开启“按请求切换模型”,以便已卸载的模型在下次使用时重新加载。",
idleActiveViaEnv:
"空闲自动卸载已通过 UNSLOTH_MODEL_IDLE_TTL 环境变量启用。",
loadError: "加载模型自动切换设置失败。",
saveError: "保存模型自动切换设置失败。",
idleError: "请输入整数秒数0 或以上)。",
idleError: "输入 0 保持模型加载,或输入至少 60 秒。",
},
previewSharing: {
sectionTitle: "预览分享",