Studio: surface the llama.cpp update affordance when MTP is disabled (#6192)

* Studio: surface the llama.cpp update affordance when MTP is disabled

When a model asks for MTP (auto on an MTP model, or forced mtp / mtp+ngram)
but it gets disabled, the load already degrades gracefully and serves without
speculative decoding. Until now the UI gave no hint why, or that an update
would fix it.

Record why MTP was dropped on the backend (spec_fallback_reason): the probe
found no mtp token (binary_no_mtp), the spawn aborted with an outdated-arch /
context-build error such as a prebuilt that predates the Gemma drafter
(binary_outdated), or the current build could not run it, e.g. a CUDA kernel
limit (runtime_error). Expose it in the inference status. In the chat
Speculative Decoding section, show a short note and, for the two update-fixable
reasons, an inline Update llama.cpp button that reuses the existing update flow.
A runtime_error gets the note without an update push, since a newer build may
not fix it.

Backend tests cover the reason being set / cleared. Frontend typechecks.

* Address review: tighten the update hint to genuinely outdated binaries

Reserve binary_outdated (which surfaces the Update llama.cpp affordance) for an
unknown-architecture abort, which proves the prebuilt predates the model;
classify the generic memory/context build failures as runtime_error, where an
update may not help. Frontend: only append the "Update llama.cpp to enable it"
sentence when an update is actually available, so the text never points at an
action the UI is not offering.
This commit is contained in:
Daniel Han 2026-06-11 06:10:17 -07:00 committed by GitHub
commit a5d6e6928d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 157 additions and 2 deletions

View file

@ -50,6 +50,7 @@ import {
TooltipTrigger,
} from "@/components/ui/tooltip";
import { useIsMobile } from "@/hooks/use-mobile";
import { useLlamaUpdateCheck } from "@/hooks/use-llama-update-check";
import { cn } from "@/lib/utils";
import {
ArrowDown01Icon,
@ -62,7 +63,7 @@ import { HugeiconsIcon } from "@hugeicons/react";
import { ChevronDown, ExternalLink } from "lucide-react";
import { Tooltip as TooltipPrimitive } from "radix-ui";
import { Fragment, type ReactNode } from "react";
import { useEffect, useMemo, useRef, useState } from "react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { toast } from "@/lib/toast";
import { OpenAICodeExecSection } from "./components/openai-code-exec-section";
import {
@ -487,6 +488,27 @@ export function ChatSettingsPanel({
const loadedSpeculativeType = useChatRuntimeStore(
(s) => s.loadedSpeculativeType,
);
const specFallbackReason = useChatRuntimeStore((s) => s.specFallbackReason);
// "binary_no_mtp" / "binary_outdated" mean a newer prebuilt would re-enable
// MTP; "runtime_error" means the current build cannot run it (no update push).
const mtpUpdatable =
specFallbackReason === "binary_no_mtp" ||
specFallbackReason === "binary_outdated";
const {
status: llamaUpdateStatus,
applying: llamaUpdating,
apply: applyLlamaUpdate,
} = useLlamaUpdateCheck({ enabled: mtpUpdatable });
const handleMtpUpdate = useCallback(async () => {
const result = await applyLlamaUpdate();
if (result.ok) {
toast.success(
`llama.cpp updated to ${result.tag ?? "the latest build"}. Reload your model to enable MTP.`,
);
} else {
toast.error(`llama.cpp update failed: ${result.error ?? "unknown error"}`);
}
}, [applyLlamaUpdate]);
const specDraftNMax = useChatRuntimeStore((s) => s.specDraftNMax);
const setSpecDraftNMax = useChatRuntimeStore((s) => s.setSpecDraftNMax);
const loadedSpecDraftNMax = useChatRuntimeStore(
@ -923,6 +945,32 @@ export function ChatSettingsPanel({
</Select>
</div>
</div>
{specFallbackReason &&
(speculativeType === "auto" ||
speculativeType === "mtp" ||
speculativeType === "mtp+ngram") && (
<div className="rounded-lg bg-amber-500/[0.08] px-3 py-2 text-[12px] leading-[1.4] text-nav-fg/80">
<p>
{specFallbackReason === "runtime_error"
? "MTP could not start for this model on the installed llama.cpp build, so it is running without speculative decoding."
: "MTP is not available in the installed llama.cpp build, so this model is running without it." +
(llamaUpdateStatus?.update_available
? " Update llama.cpp to enable it."
: "")}
</p>
{mtpUpdatable && llamaUpdateStatus?.update_available && (
<Button
size="sm"
className="corner-squircle mt-2 h-7 text-[12px]"
onClick={handleMtpUpdate}
disabled={llamaUpdating}
data-test-id="mtp-update-button"
>
{llamaUpdating ? "Updating..." : "Update llama.cpp"}
</Button>
)}
</div>
)}
{(speculativeType === "mtp" ||
speculativeType === "mtp+ngram") && (
<div className="flex items-center justify-between gap-3">

View file

@ -412,6 +412,7 @@ export function useChatModelRuntime() {
statusRes.requires_trust_remote_code ?? false,
defaultChatTemplate: nextDefaultChatTemplate,
loadedIsMultimodal: isMultimodalResponse(statusRes),
specFallbackReason: statusRes.spec_fallback_reason ?? null,
...(prevState.loadedSpeculativeType === null && {
speculativeType: currentSpecType,
loadedSpeculativeType: currentSpecType,

View file

@ -416,6 +416,11 @@ type ChatRuntimeStore = {
loadedKvCacheDtype: string | null;
speculativeType: string | null;
loadedSpeculativeType: string | null;
/**
* Why MTP was disabled on the loaded model despite being requested, or null.
* Mirrors InferenceStatusResponse.spec_fallback_reason.
*/
specFallbackReason: string | null;
/** User --spec-draft-n-max override (null = platform default). */
specDraftNMax: number | null;
loadedSpecDraftNMax: number | null;
@ -765,6 +770,7 @@ export const useChatRuntimeStore = create<ChatRuntimeStore>((set, get) => ({
loadedKvCacheDtype: null,
speculativeType: "auto",
loadedSpeculativeType: null,
specFallbackReason: null,
specDraftNMax: null,
loadedSpecDraftNMax: null,
loadedIsMultimodal: false,
@ -977,6 +983,7 @@ export const useChatRuntimeStore = create<ChatRuntimeStore>((set, get) => ({
loadedKvCacheDtype: null,
speculativeType: "auto",
loadedSpeculativeType: null,
specFallbackReason: null,
specDraftNMax: null,
loadedSpecDraftNMax: null,
loadedIsMultimodal: false,

View file

@ -174,6 +174,12 @@ export interface InferenceStatusResponse {
/** Canonical UI-facing mode currently active. See LoadModelRequest. */
speculative_type?: string | null;
spec_draft_n_max?: number | null;
/**
* Why MTP was disabled on the loaded model despite being requested.
* "binary_no_mtp" / "binary_outdated" -> updating llama.cpp would re-enable
* it; "runtime_error" -> the current build could not run it. Null otherwise.
*/
spec_fallback_reason?: string | null;
}
export interface AudioGenerationResponse {