mirror of
https://github.com/mudler/LocalAI.git
synced 2026-05-17 21:21:23 -04:00
* feat(llama-cpp): bump to MTP-merge SHA and document draft-mtp spec type Update LLAMA_VERSION to 0253fb21 (post ggml-org/llama.cpp#22673 merge, 2026-05-16) to pick up Multi-Token Prediction support. No grpc-server.cpp changes are required: the existing `spec_type` option delegates to upstream's `common_speculative_types_from_names()`, which already accepts the new `draft-mtp` name. The `n_rs_seq` cparam needed by MTP is auto-derived inside `common_context_params_to_llama` from `params.speculative.need_n_rs_seq()`, and when no `draft_model` is set the upstream server builds the MTP context off the target model itself. Docs: extend the speculative-decoding section of the model-configuration guide with the new type, both load paths (MTP head embedded in the main GGUF vs. separate `mtp-*.gguf` sibling), the PR's recommended `spec_n_max:2-3`, and the chained `draft-mtp,ngram-mod` recipe. Also notes that the upstream `-hf` auto-discovery of `mtp-*.gguf` siblings is not wired through LocalAI's gRPC layer. Agent guide: short note explaining that new upstream spec types are picked up automatically and that MTP needs no gRPC plumbing. Assisted-by: Claude:claude-opus-4-7 [Claude Code] Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * feat(llama-cpp): auto-detect MTP heads and enable draft-mtp on import + load Detect upstream's `<arch>.nextn_predict_layers` GGUF metadata key (set by `convert_hf_to_gguf.py` for Qwen3.5/3.6 family models and similar) and, when present and the user has not configured a `spec_type` explicitly, auto-append the upstream-recommended speculative-decoding tuple: - spec_type:draft-mtp - spec_n_max:6 - spec_p_min:0.75 The 0.75 p_min is pinned defensively because upstream marks the current default with a "change to 0.0f" TODO; locking it here keeps acceptance thresholds stable across future llama.cpp bumps. Detection runs in two places: - The model importer (`POST /models/import-uri`, the `/import-model` UI) range-fetches the GGUF header for HuggingFace / direct-URL imports via `gguf.ParseGGUFFileRemote`, with a 30s timeout and non-fatal error handling. OCI/Ollama URIs are skipped because the artifact is not directly streamable; the load-time hook covers them once the file is on disk. - The llama-cpp load-time hook (`guessGGUFFromFile`) reads the local header on every model start and appends the same options if `spec_type` is not already set. Both paths share `ApplyMTPDefaults` and respect an explicit user-set `spec_type:` / `speculative_type:` so YAML overrides win. Ginkgo specs cover the append, preserve-user-choice, legacy alias, and nil safety paths. Assisted-by: Claude:claude-opus-4-7 [Claude Code] Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fix(importer): resolve huggingface:// URIs before MTP header probe `gguf.ParseGGUFFileRemote` only speaks HTTP(S), but the importer was handing it the raw `huggingface://...` URI directly (and similarly for any other custom downloader scheme). Live-test against `huggingface://ggml-org/Qwen3.6-27B-MTP-GGUF/Qwen3.6-27B-MTP-Q8_0.gguf` exposed this: the probe failed with `unsupported protocol scheme "huggingface"`, was caught by the non-fatal error path, and the MTP options were silently never applied to the generated YAML. Route every candidate URI through `downloader.URI.ResolveURL()` and require the resolved form to be HTTP(S). After the fix the probe successfully reads `<arch>.nextn_predict_layers=1` from the real HF GGUF and the emitted ConfigFile carries spec_type:draft-mtp, spec_n_max:6, spec_p_min:0.75 as intended. Assisted-by: Claude:claude-opus-4-7 [Claude Code] Signed-off-by: Ettore Di Giacinto <mudler@localai.io> --------- Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Co-authored-by: Ettore Di Giacinto <mudler@localai.io>
85 lines
2.8 KiB
Go
85 lines
2.8 KiB
Go
package config
|
|
|
|
import (
|
|
"strings"
|
|
|
|
gguf "github.com/gpustack/gguf-parser-go"
|
|
"github.com/mudler/xlog"
|
|
)
|
|
|
|
// mtpSpecOptions lists the speculative-decoding option keys auto-applied when
|
|
// an MTP head is detected on a llama-cpp GGUF. Defaults track the upstream
|
|
// MTP PR (ggml-org/llama.cpp#22673):
|
|
//
|
|
// - spec_type:draft-mtp activates Multi-Token Prediction
|
|
// - spec_n_max:6 draft window
|
|
// - spec_p_min:0.75 pinned because upstream marked the 0.75 default
|
|
// with a "change to 0.0f" TODO; locking it here keeps acceptance
|
|
// thresholds stable across future bumps
|
|
var mtpSpecOptions = []string{
|
|
"spec_type:draft-mtp",
|
|
"spec_n_max:6",
|
|
"spec_p_min:0.75",
|
|
}
|
|
|
|
// MTPSpecOptions returns a copy of the option keys auto-applied when an MTP
|
|
// head is detected. Exported for testing and for the importer.
|
|
func MTPSpecOptions() []string {
|
|
out := make([]string, len(mtpSpecOptions))
|
|
copy(out, mtpSpecOptions)
|
|
return out
|
|
}
|
|
|
|
// HasEmbeddedMTPHead reports whether the parsed GGUF declares a Multi-Token
|
|
// Prediction head. Detection reads `<arch>.nextn_predict_layers`, which is
|
|
// what `gguf_writer.add_nextn_predict_layers(n)` emits in upstream's
|
|
// `conversion/qwen.py` MTP mixin. A positive layer count means the head is
|
|
// present in the same GGUF as the trunk.
|
|
func HasEmbeddedMTPHead(f *gguf.GGUFFile) (uint32, bool) {
|
|
if f == nil {
|
|
return 0, false
|
|
}
|
|
arch := f.Architecture().Architecture
|
|
if arch == "" {
|
|
return 0, false
|
|
}
|
|
v, ok := f.Header.MetadataKV.Get(arch + ".nextn_predict_layers")
|
|
if !ok {
|
|
return 0, false
|
|
}
|
|
n := gguf.ValueNumeric[uint32](v)
|
|
return n, n > 0
|
|
}
|
|
|
|
// hasSpecTypeOption returns true when the slice already contains a
|
|
// user-configured `spec_type:` / `speculative_type:` entry. Used to avoid
|
|
// clobbering an explicit choice with the MTP auto-defaults.
|
|
func hasSpecTypeOption(opts []string) bool {
|
|
for _, o := range opts {
|
|
if strings.HasPrefix(o, "spec_type:") || strings.HasPrefix(o, "speculative_type:") {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// ApplyMTPDefaults appends the auto-MTP option keys to cfg.Options when none
|
|
// is already configured. It is a no-op when the user already picked a
|
|
// `spec_type` (either via YAML or via the importer's preferences flow).
|
|
//
|
|
// `layers` is the value read from `<arch>.nextn_predict_layers` and is only
|
|
// used for the diagnostic log line.
|
|
func ApplyMTPDefaults(cfg *ModelConfig, layers uint32) {
|
|
if cfg == nil {
|
|
return
|
|
}
|
|
if hasSpecTypeOption(cfg.Options) {
|
|
xlog.Debug("[mtp] embedded MTP head detected but spec_type already configured; leaving user choice intact",
|
|
"name", cfg.Name, "nextn_layers", layers)
|
|
return
|
|
}
|
|
cfg.Options = append(cfg.Options, mtpSpecOptions...)
|
|
xlog.Info("[mtp] embedded MTP head detected; enabling draft-mtp speculative decoding",
|
|
"name", cfg.Name, "nextn_layers", layers, "spec_n_max", 6, "spec_p_min", 0.75)
|
|
}
|