From c00a993a68cf98491080a191bf453a20a90c08a2 Mon Sep 17 00:00:00 2001 From: Daniel Han Date: Tue, 17 Mar 2026 01:22:08 -0700 Subject: [PATCH] studio: fix stale GGUF metadata, update helper model, auth improvements (#4346) * studio: switch helper model to Qwen3.5-4B-GGUF Replace Qwen3-4B-Instruct-2507-GGUF with Qwen3.5-4B-GGUF as the default helper model for LLM-assisted dataset detection. Same UD-Q4_K_XL variant. * studio: fix stale GGUF metadata when switching models (#4347) Reset _supports_reasoning, _supports_tools, _context_length, and _chat_template at the start of _read_gguf_metadata() to prevent stale settings from a previous model leaking into the next load. Co-authored-by: Daniel Han * studio: change login error to "Incorrect password", add reset-password CLI - Login error now says "Incorrect password" instead of the generic "Incorrect username or password" since Studio only has one account. - Add `unsloth studio reset-password` command that deletes the auth database so a fresh admin account with a new random password is created on the next server start. * studio: include reset command in login error message * studio: change password setup subtitle wording --- cli/commands/studio.py | 25 +++++++++++++++++++ studio/backend/core/inference/llama_cpp.py | 7 ++++++ studio/backend/routes/auth.py | 4 +-- studio/backend/utils/datasets/llm_assist.py | 2 +- .../features/auth/components/auth-form.tsx | 2 +- 5 files changed, 36 insertions(+), 4 deletions(-) diff --git a/cli/commands/studio.py b/cli/commands/studio.py index 89b2227fa6..da5198ad75 100644 --- a/cli/commands/studio.py +++ b/cli/commands/studio.py @@ -178,3 +178,28 @@ def setup(): if result.returncode != 0: raise typer.Exit(result.returncode) + + +# ── unsloth studio reset-password ──────────────────────────────────── + + +@studio_app.command("reset-password") +def reset_password(): + """Reset the Studio admin password. + + Deletes the auth database so that a fresh admin account with a new + random password is created on the next server start. The Studio + server must be restarted after running this command. + """ + auth_dir = STUDIO_HOME / "auth" + db_file = auth_dir / "auth.db" + pw_file = auth_dir / ".bootstrap_password" + + if not db_file.exists(): + typer.echo("No auth database found -- nothing to reset.") + raise typer.Exit(0) + + db_file.unlink(missing_ok = True) + pw_file.unlink(missing_ok = True) + + typer.echo("Auth database deleted. Restart Unsloth Studio to get a new password.") diff --git a/studio/backend/core/inference/llama_cpp.py b/studio/backend/core/inference/llama_cpp.py index 08126dedf7..410305d138 100644 --- a/studio/backend/core/inference/llama_cpp.py +++ b/studio/backend/core/inference/llama_cpp.py @@ -446,6 +446,13 @@ class LlamaCppBackend: Parses only the KV pairs we need (~30ms even for multi-GB files). For split GGUFs, metadata is always in shard 1. """ + # Reset metadata from any previously loaded model so stale flags + # (eg _supports_reasoning) do not carry over when switching models. + self._context_length = None + self._chat_template = None + self._supports_reasoning = False + self._supports_tools = False + try: WANTED = {"general.architecture", "tokenizer.chat_template"} arch = None diff --git a/studio/backend/routes/auth.py b/studio/backend/routes/auth.py index 4b586d1432..db37ed837d 100644 --- a/studio/backend/routes/auth.py +++ b/studio/backend/routes/auth.py @@ -54,14 +54,14 @@ async def login(payload: AuthLoginRequest) -> Token: if record is None: raise HTTPException( status_code = status.HTTP_401_UNAUTHORIZED, - detail = "Incorrect username or password", + detail = "Incorrect password. Run 'unsloth studio reset-password' in your terminal to reset it.", ) salt, pwd_hash, _jwt_secret, must_change_password = record if not hashing.verify_password(payload.password, salt, pwd_hash): raise HTTPException( status_code = status.HTTP_401_UNAUTHORIZED, - detail = "Incorrect username or password", + detail = "Incorrect password. Run 'unsloth studio reset-password' in your terminal to reset it.", ) access_token = create_access_token(subject = payload.username) diff --git a/studio/backend/utils/datasets/llm_assist.py b/studio/backend/utils/datasets/llm_assist.py index 5012c8ba07..1dc0c495e5 100644 --- a/studio/backend/utils/datasets/llm_assist.py +++ b/studio/backend/utils/datasets/llm_assist.py @@ -26,7 +26,7 @@ from loggers import get_logger logger = get_logger(__name__) -DEFAULT_HELPER_MODEL_REPO = "unsloth/Qwen3-4B-Instruct-2507-GGUF" +DEFAULT_HELPER_MODEL_REPO = "unsloth/Qwen3.5-4B-GGUF" DEFAULT_HELPER_MODEL_VARIANT = "UD-Q4_K_XL" README_MAX_CHARS = 1500 diff --git a/studio/frontend/src/features/auth/components/auth-form.tsx b/studio/frontend/src/features/auth/components/auth-form.tsx index 56ff0a8c2b..7d4363c1bf 100644 --- a/studio/frontend/src/features/auth/components/auth-form.tsx +++ b/studio/frontend/src/features/auth/components/auth-form.tsx @@ -172,7 +172,7 @@ export function AuthForm({ mode }: AuthFormProps): ReactElement | null { const title = isLoginMode ? "Welcome back" : "Setup your account"; const subtitle = isLoginMode ? "Sign in with your password." - : "Choose your new password."; + : "Choose a new password"; const submitLabel = isLoginMode ? "Login" : "Change password"; const showSwitchLink = !isLoginMode; const switchText = "Password already setup? ";