import { useState } from "react" import { X, Check, Zap, AlertTriangle } from "lucide-react" import { api, type ModelInfo, type DraftInfo } from "@/lib/api" import { useDrafts } from "@/lib/queries" import { fmtSize } from "@/lib/format" import { cn } from "@/lib/utils" /** * Idiotensichere Speculative-Decoding-Konfiguration für EIN Modell. * Zeigt nur VOCAB-KOMPATIBLE Drafts als wählbar; inkompatible werden gesperrt * und mit Begründung angezeigt. So kann nie ein kaputter Draft gesetzt werden * (der das Modell beim Laden scheitern ließe). */ export function SpecDraftModal({ model, onClose, onChanged, }: { model: ModelInfo; onClose: () => void; onChanged: () => void }) { const { data, isLoading } = useDrafts(model.gguf_path) const [busy, setBusy] = useState(null) const [err, setErr] = useState("") const tv = data?.target_vocab const drafts = data?.drafts ?? [] const compatibles = drafts.filter((d) => d.compatible === true) const currentFile = model.spec_draft_model async function apply(draftPath: string | null) { setBusy(draftPath ?? "__clear__") setErr("") try { await api(`/api/models/${encodeURIComponent(model.name)}/draft`, { method: "POST", body: JSON.stringify({ draft_path: draftPath }), }) onChanged() onClose() } catch (e: any) { setErr(String(e?.message || e)) setBusy(null) } } const vocabLabel = (v?: { pre: string | null; n_vocab: number | null } | null) => v ? `${v.pre ?? "?"} · ${v.n_vocab?.toLocaleString() ?? "?"} Tokens` : "—" return (

Speculative Draft

Beschleunigt die Token-Generierung mit einem kleinen "Draft"-Modell. Voraussetzung: der Draft muss den exakt gleichen Tokenizer (Vocab) haben wie das Modell — sonst lehnt llama.cpp es ab.
{/* Ziel-Vocab */}
{model.name.split("/").pop()?.replace(/\.gguf$/i, "")} Vocab: {vocabLabel(tv)}
{/* Aktueller Zustand */} {model.spec_active && currentFile && (
Aktiv: {currentFile}
)} {!data?.target_exists && (
Modell-GGUF noch nicht vorhanden — Spec-Draft ist nach dem Download konfigurierbar.
)} {/* Draft-Liste */}
{isLoading ? (
Prüfe Vocab-Kompatibilität…
) : drafts.length === 0 ? (
Keine Draft-Modelle in /srv/models/drafts. Lade ein kleines same-family-Modell (z.B. Qwen3-0.6B) und lege es dort ab.
) : ( drafts.map((d: DraftInfo) => { const isCurrent = d.filename === currentFile const ok = d.compatible === true return (
{d.filename}
{fmtSize(d.size_bytes)} · Vocab: {vocabLabel(d.vocab)}
{ok ? ( isCurrent ? ( Aktiv ) : ( ) ) : ( {d.compatible === false ? "Vocab ≠" : "n/a"} )}
) }) )}
{/* Hinweis, wenn Drafts da sind aber keiner kompatibel */} {!isLoading && data?.target_exists && drafts.length > 0 && compatibles.length === 0 && (
Kein vocab-kompatibler Draft verfügbar — Speculative Decoding ist für dieses Modell nicht möglich. Es braucht einen Draft mit identischem Tokenizer (pre={tv?.pre}, n_vocab={tv?.n_vocab?.toLocaleString()}).
)} {err &&
{err}
}
) }