internal/autogen/flagtable.go
Rama main · réplica de lectura del repositorio original.
package autogen
import (
"fmt"
"strings"
)
// This file is the single description of the llama-server flags the emitter can
// produce, each grouped under the setting it controls (its "knob"). It is the
// source of three behaviours:
//
// - composition (customargs.go): a knob present in the user's custom launch
// arguments drops every generated occurrence of that knob,
// - ownership (config editor): a control is token-owned when the custom text
// carries this knob's flag, so the form never silently disagrees with the
// text,
// - validation: a flag missing from the table and from the selected backend's
// --help is an error, and autocomplete comes from the union.
//
// Adding a flag to an emitter without adding it here is a test failure. That
// test is the whole point: -cram shipped without a parser case and duplicated
// itself on every save, and the same class of bug has recurred with every new
// emitter flag since.
//
// Aliases are the other spellings llama-server accepts for the same setting
// (its getopt pairs almost every short flag with a long one). They matter for
// suppression: a user's `--ctx-size 32768` must drop the generated `-c 8192`,
// not duplicate it. Only well-known aliases belong here; a spelling llama.cpp
// does not document would suppress a generated flag the user meant to keep.
// RepeatPolicy says what a custom occurrence does to the generated ones.
type RepeatPolicy int
const (
// ReplaceAll: the custom occurrence replaces every generated occurrence of
// the knob. This is right for every single-valued flag and also for
// --spec-type, whose generated occurrences form one chain that a custom
// value must not half-replace.
ReplaceAll RepeatPolicy = iota
// Additive: llama-server accumulates multiple values (--lora, --alias), so a
// custom occurrence must not drop the generated ones. Nothing the emitter
// writes uses this yet; it exists so validation and the editor can tell the
// two cases apart.
Additive
)
// FlagDef describes one flag the launch-args layer understands.
type FlagDef struct {
Name string // canonical spelling, as the emitter writes it
Aliases []string // other spellings llama-server accepts for the same knob
Knob string // setting the flag controls; "" = structural, no override field
Value bool // takes at least one value token
// ExtraValues is how many value tokens follow the first. Only
// --lora-scaled (FNAME SCALE) needs it today. It matters because the
// token walk in customargs.go consumes a flag's values along with it: an
// under-counted flag leaves its trailing value stranded as a bare token,
// which then looks like a positional argument to everything downstream.
ExtraValues int
Repeat RepeatPolicy
}
// llamaFlagTable lists every llama-server flag quartermaster emits, plus the
// legacy spellings the config form still understands (--no-mmap, --mlock,
// -dio). Keep it sorted by area, not alphabetically: the areas are what a
// reader checks against the emitter.
var llamaFlagTable = []FlagDef{
// Model / server identity.
{Name: "-m", Aliases: []string{"--model"}, Knob: "model", Value: true},
{Name: "--port", Knob: "port", Value: true},
{Name: "--host", Knob: "host", Value: true},
{Name: "--cors-origins", Knob: "corsOrigins", Value: true},
{Name: "--device", Knob: "device", Value: true},
// Sizing: layers, context, batches, slots.
{Name: "-ngl", Aliases: []string{"--n-gpu-layers"}, Knob: "ngl", Value: true},
{Name: "-c", Aliases: []string{"--ctx-size"}, Knob: "ctx", Value: true},
{Name: "-ub", Aliases: []string{"--ubatch-size"}, Knob: "ub", Value: true},
{Name: "-b", Aliases: []string{"--batch-size"}, Knob: "batch", Value: true},
{Name: "--parallel", Aliases: []string{"-np"}, Knob: "parallel", Value: true},
{Name: "--n-cpu-moe", Aliases: []string{"-ncmoe"}, Knob: "nCpuMoe", Value: true},
// KV cache / attention.
{Name: "-fa", Aliases: []string{"--flash-attn"}, Knob: "flashAttn", Value: true},
{Name: "-ctk", Aliases: []string{"--cache-type-k"}, Knob: "kvK", Value: true},
{Name: "-ctv", Aliases: []string{"--cache-type-v"}, Knob: "kvV", Value: true},
{Name: "--kv-unified", Knob: "kvUnified"},
{Name: "--no-kv-offload", Aliases: []string{"-nkvo"}, Knob: "kvInRam"},
// Load mode. --no-mmap / --mlock / -dio are the pre---load-mode spellings;
// the emitter writes --load-mode, but an old sidecar or a hand-written
// command may still carry them and they own the same knob.
{Name: "--load-mode", Aliases: []string{"-lm"}, Knob: "loadMode", Value: true},
{Name: "--no-mmap", Knob: "loadMode"},
{Name: "--mlock", Knob: "loadMode"},
{Name: "-dio", Aliases: []string{"--direct-io"}, Knob: "loadMode"},
// Server hygiene (always emitted, no override field).
{Name: "--no-warmup", Knob: "noWarmup"},
{Name: "--no-ui", Aliases: []string{"--no-webui"}, Knob: "noUi"},
{Name: "--metrics", Knob: "metrics"},
{Name: "--props", Knob: "props"},
// Vision projector.
{Name: "--mmproj", Knob: "mmprojFile", Value: true},
{Name: "--no-mmproj-offload", Knob: "mmprojOffload"},
// Multi-GPU placement.
{Name: "-sm", Aliases: []string{"--split-mode"}, Knob: "splitMode", Value: true},
{Name: "-ts", Aliases: []string{"--tensor-split"}, Knob: "tensorSplit", Value: true},
{Name: "-mg", Aliases: []string{"--main-gpu"}, Knob: "mainGpu", Value: true},
// Speculative decoding. --spec-type is emitted once per chained backend and
// is still ReplaceAll: a custom value replaces the whole chain.
{Name: "--spec-type", Knob: "spec", Value: true, Repeat: ReplaceAll},
{Name: "--spec-default", Knob: "specDefault"},
{Name: "--spec-draft-n-max", Knob: "specDraftNMax", Value: true},
{Name: "--spec-draft-n-min", Knob: "specDraftNMin", Value: true},
{Name: "--spec-ngram-map-k4v-size-n", Knob: "specNgramSizeN", Value: true},
{Name: "--spec-ngram-map-k4v-size-m", Knob: "specNgramSizeM", Value: true},
{Name: "--spec-ngram-map-k4v-min-hits", Knob: "specNgramMinHits", Value: true},
{Name: "-md", Aliases: []string{"--model-draft"}, Knob: "draftModel", Value: true},
{Name: "-ngld", Aliases: []string{"--n-gpu-layers-draft"}, Knob: "ngld", Value: true},
{Name: "-ctkd", Aliases: []string{"--cache-type-k-draft"}, Knob: "kvKDraft", Value: true},
{Name: "-ctvd", Aliases: []string{"--cache-type-v-draft"}, Knob: "kvVDraft", Value: true},
// Reasoning / templates.
{Name: "--jinja", Knob: "jinja"},
{Name: "--reasoning-format", Knob: "reasoningFmt", Value: true},
{Name: "--reasoning", Knob: "reasoningFmt", Value: true},
{Name: "--reasoning-budget", Knob: "reasoningBudget", Value: true},
{Name: "--reasoning-preserve", Knob: "reasoningPreserve"},
{Name: "--no-reasoning-preserve", Knob: "reasoningPreserve"},
{Name: "--chat-template-file", Knob: "chatTemplateFile", Value: true},
{Name: "--chat-template-kwargs", Knob: "chatTemplateKwargs", Value: true},
// Context checkpoints.
{Name: "--ctx-checkpoints", Knob: "ctxCheckpoints", Value: true},
{Name: "-cms", Aliases: []string{"--checkpoint-min-step"}, Knob: "checkpointMinStep", Value: true},
// DRY sampler.
{Name: "--dry-multiplier", Knob: "dryMultiplier", Value: true},
{Name: "--dry-base", Knob: "dryBase", Value: true},
{Name: "--dry-allowed-length", Knob: "dryAllowedLength", Value: true},
// Sampler defaults.
{Name: "--temp", Aliases: []string{"--temperature"}, Knob: "temp", Value: true},
{Name: "--top-k", Knob: "topK", Value: true},
{Name: "--top-p", Knob: "topP", Value: true},
{Name: "--min-p", Knob: "minP", Value: true},
{Name: "--presence-penalty", Knob: "presencePenalty", Value: true},
// Threads, priority, slot cache.
{Name: "-t", Aliases: []string{"--threads"}, Knob: "threads", Value: true},
{Name: "-tb", Aliases: []string{"--threads-batch"}, Knob: "threadsBatch", Value: true},
{Name: "--prio", Knob: "prio", Value: true},
{Name: "--slot-save-path", Knob: "slotSavePath", Value: true},
// Engine knobs.
{Name: "--no-op-offload", Knob: "noOpOffload"},
{Name: "--no-repack", Knob: "noRepack"},
{Name: "--cache-reuse", Knob: "cacheReuse", Value: true},
{Name: "-cram", Aliases: []string{"--cache-ram"}, Knob: "cacheRam", Value: true},
{Name: "-lv", Aliases: []string{"--log-verbosity"}, Knob: "logVerbosity", Value: true},
{Name: "--cache-idle-slots", Knob: "cacheIdleSlots"},
{Name: "--no-cache-idle-slots", Knob: "cacheIdleSlots"},
{Name: "--swa-full", Knob: "swaFull"},
{Name: "--context-shift", Knob: "contextShift"},
{Name: "--no-context-shift", Knob: "contextShift"},
{Name: "-sps", Aliases: []string{"--slot-prompt-similarity"}, Knob: "slotPromptSimilarity", Value: true},
{Name: "--rope-scaling", Knob: "ropeScaling", Value: true},
{Name: "--rope-scale", Knob: "ropeScale", Value: true},
{Name: "--rope-freq-base", Knob: "ropeFreqBase", Value: true},
{Name: "--yarn-orig-ctx", Knob: "yarnOrigCtx", Value: true},
{Name: "-ot", Aliases: []string{"--override-tensor"}, Knob: "overrideTensor", Value: true},
// LoRA adapters. Additive, unlike almost everything else here: llama-server
// accumulates --lora occurrences, so a user who adds one in custom args
// means "and this one too", not "instead of the model's". Both spellings
// share the knob so ownership is coherent either way.
{Name: "--lora", Knob: "loras", Value: true, Repeat: Additive},
{Name: "--lora-scaled", Knob: "loras", Value: true, ExtraValues: 1, Repeat: Additive},
}
// FlagTable is ONE backend's flag vocabulary: the ordered defs plus an index
// over every spelling they accept. There are two of them (llama-server here,
// sd-server in flagtable_sd.go) because a spelling only means something against
// a specific binary: -H is --height to sd-server and --hf-repo to llama-server,
// and -l is --listen-ip to one and nothing at all to the other. Resolving a
// diffusion command against the llama table is not a near miss, it is how every
// sd-server flag read as unknown, owned no knob, suppressed nothing, and left
// the user's text to pile up beside the generated copy.
type FlagTable struct {
Backend string // the binary these spellings belong to, for error text
Defs []FlagDef
index map[string]FlagDef
}
// newFlagTable builds the spelling index once, at package init.
func newFlagTable(backend string, defs []FlagDef) *FlagTable {
t := &FlagTable{Backend: backend, Defs: defs, index: make(map[string]FlagDef, len(defs)*2)}
for _, d := range defs {
t.index[d.Name] = d
for _, a := range d.Aliases {
t.index[a] = d
}
}
return t
}
// Lookup resolves a flag spelling to its definition. ok is false for a flag the
// table does not know, which is not an error by itself: it may be a
// user-written flag with no emitter counterpart (validate.go checks those
// against the backend's --help).
func (t *FlagTable) Lookup(name string) (FlagDef, bool) {
if t == nil {
return FlagDef{}, false
}
d, ok := t.index[name]
return d, ok
}
// LookupFlag resolves a spelling against the llama-server table. It and the
// other three package-level wrappers below exist so the dozen llama-only
// callers (pins.go, validate.go, the estimate preview) read exactly as they did
// before the table became a parameter; anything that can serve either backend
// takes a *FlagTable instead.
func LookupFlag(name string) (FlagDef, bool) { return LlamaFlags.Lookup(name) }
// SplitFlagToken splits a token into flag name and inline value, accepting
// llama's `--flag=value` form. hasValue is true when the value was inline, so
// the caller knows not to consume the next token.
func SplitFlagToken(tok string) (name, value string, hasValue bool) {
if !strings.HasPrefix(tok, "--") {
return tok, "", false
}
if i := strings.IndexByte(tok, '='); i >= 0 {
return tok[:i], tok[i+1:], true
}
return tok, "", false
}
// TokenKnob returns the knob a command token belongs to, resolving both
// spellings and the --flag=value form. "" for values, for structural flags, and
// for anything the table does not know.
func (t *FlagTable) TokenKnob(tok string) string {
if !strings.HasPrefix(tok, "-") {
return ""
}
name, _, _ := SplitFlagToken(tok)
if d, ok := t.Lookup(name); ok {
return d.Knob
}
return ""
}
// TokenKnob resolves against the llama-server table.
func TokenKnob(tok string) string { return LlamaFlags.TokenKnob(tok) }
// OwnedKnobs returns the knobs a token list sets. Flag tokens carry their knob;
// an unknown flag contributes nothing (it cannot suppress a generated flag the
// table cannot map it to). The token after a known value-taking flag is skipped
// even when it starts with "-", so a negative value never reads as a flag.
func (t *FlagTable) OwnedKnobs(tokens []string) map[string]bool {
owned := map[string]bool{}
for i := 0; i < len(tokens); i++ {
tok := tokens[i]
if !strings.HasPrefix(tok, "-") {
continue
}
name, _, inline := SplitFlagToken(tok)
d, ok := t.Lookup(name)
if !ok {
continue
}
// An Additive flag is never "owned": llama-server accumulates its
// occurrences, so a custom --lora means "and this one too". Marking it
// owned would drop every generated occurrence AND have
// ClearOwnedFields wipe the model's adapter list, turning an addition
// into a silent replacement.
if d.Knob != "" && d.Repeat != Additive {
owned[d.Knob] = true
}
if d.Value && !inline {
i += 1 + d.ExtraValues
}
}
return owned
}
// OwnedKnobs resolves against the llama-server table.
func OwnedKnobs(tokens []string) map[string]bool { return LlamaFlags.OwnedKnobs(tokens) }
// ClearOwnedFields zeroes every structured override field whose knob the custom
// text owns, and returns the knobs it cleared (sorted by the caller). The
// fields would otherwise sit invisible under the user's flag and resurface the
// moment the flag is deleted from the text, which is issue #38's mmap reset in
// another costume.
//
// One knob can span several fields (loadMode covers Mmap, Mlock and DirectIo;
// dry covers the flag plus its three values), so the switch clears them as a
// group.
func ClearOwnedFields(ov *Override, owned map[string]bool) []string {
if ov == nil {
return nil
}
var cleared []string
clear := func(knob string, fields ...any) {
if !owned[knob] {
return
}
for _, f := range fields {
switch p := f.(type) {
case *string:
*p = ""
case *int:
*p = 0
case *float64:
*p = 0
case *bool:
*p = false
case **int:
*p = nil
case **float64:
*p = nil
case **bool:
*p = nil
default:
// A typo'd or missing pointer clears nothing silently, which is
// how issue #38's mmap pin survived a save it should not have.
panic(fmt.Sprintf("ClearOwnedFields: unsupported field type %T for knob %q", f, knob))
}
}
cleared = append(cleared, knob)
}
clear("ctx", &ov.Ctx)
clear("ub", &ov.Ub)
clear("parallel", &ov.Parallel)
clear("flashAttn", &ov.FlashAttn)
clear("kvK", &ov.KvK)
clear("kvV", &ov.KvV)
clear("kvInRam", &ov.KvInRam)
clear("loadMode", &ov.Mmap, &ov.Mlock, &ov.DirectIo)
clear("spec", &ov.Spec)
clear("specDefault", &ov.SpecDefault)
clear("specDraftNMax", &ov.SpecDraftNMax)
clear("specDraftNMin", &ov.SpecDraftNMin)
clear("specNgramSizeN", &ov.SpecNgramSizeN)
clear("specNgramSizeM", &ov.SpecNgramSizeM)
clear("specNgramMinHits", &ov.SpecNgramMinHits)
clear("kvKDraft", &ov.KvKDraft)
clear("kvVDraft", &ov.KvVDraft)
clear("reasoningFmt", &ov.ReasoningFmt)
clear("reasoningBudget", &ov.ReasoningBudget)
clear("reasoningPreserve", &ov.PreserveThinking)
clear("ctxCheckpoints", &ov.CtxCheckpoints)
clear("checkpointMinStep", &ov.CheckpointMinStep)
clear("dryMultiplier", &ov.DryMultiplier)
clear("dryBase", &ov.DryBase)
clear("dryAllowedLength", &ov.DryAllowedLength)
clear("temp", &ov.Temp)
clear("topK", &ov.TopK)
clear("topP", &ov.TopP)
clear("minP", &ov.MinP)
clear("presencePenalty", &ov.PresencePenalty)
clear("threads", &ov.Threads)
clear("threadsBatch", &ov.ThreadsBatch)
clear("prio", &ov.Prio)
clear("noOpOffload", &ov.NoOpOffload)
clear("noRepack", &ov.NoRepack)
clear("cacheReuse", &ov.CacheReuse)
clear("cacheRam", &ov.CacheRamMB)
clear("logVerbosity", &ov.LogVerbosity)
clear("cacheIdleSlots", &ov.CacheIdleSlots)
clear("swaFull", &ov.SwaFull)
clear("contextShift", &ov.ContextShift)
clear("slotPromptSimilarity", &ov.SlotPromptSimilarity)
clear("ropeScaling", &ov.RopeScaling)
clear("ropeScale", &ov.RopeScale)
clear("ropeFreqBase", &ov.RopeFreqBase)
clear("yarnOrigCtx", &ov.YarnOrigCtx)
clear("splitMode", &ov.SplitMode)
clear("tensorSplit", &ov.TensorSplit)
clear("mainGpu", &ov.MainGpu)
clear("overrideTensor", &ov.OverrideTensor)
clear("chatTemplateFile", &ov.ChatTemplateFile)
clear("mmprojFile", &ov.MmprojFile)
clear("mmprojOffload", &ov.Mmproj)
clear("slotSavePath", &ov.SlotCache)
// sd-server knobs. They share this switch rather than getting a twin of it
// because an Override carries BOTH backends' fields and only one table is
// ever consulted for a given model, so a llama knob set can never name one of
// these. Flags with no field behind them (--max-vram, --params-backend,
// --auto-fit) are absent on purpose: they suppress their generated copy
// through the table and have nothing to clear.
clear("vaePath", &ov.VaePath)
clear("audioVaePath", &ov.AudioVaePath)
clear("clipLPath", &ov.ClipLPath)
clear("clipGPath", &ov.ClipGPath)
clear("t5Path", &ov.T5Path)
clear("textEncoderPath", &ov.TextEncoderPath)
// llm_vision owns the gate as well as the path: leaving LlmVision "on" under
// a hand-written --llm_vision would re-derive a projector beside the encoder
// and emit a second copy of the flag.
clear("llmVisionPath", &ov.LlmVisionPath, &ov.LlmVision)
clear("loraDir", &ov.LoraDir)
clear("offloadToCpu", &ov.OffloadToCpu)
clear("vaeOnCpu", &ov.VaeOnCpu)
clear("teOnCpu", &ov.TeOnCpu)
clear("streamLayers", &ov.StreamLayers)
clear("diffusionFa", &ov.DiffusionFa)
clear("vaeTiling", &ov.VaeTiling)
clear("temporalTiling", &ov.TemporalTiling)
clear("defaultSteps", &ov.DefaultSteps)
clear("defaultCfg", &ov.DefaultCfg)
clear("defaultSampler", &ov.DefaultSampler)
clear("defaultWidth", &ov.DefaultWidth)
clear("defaultHeight", &ov.DefaultHeight)
clear("defaultFrames", &ov.DefaultFrames)
clear("defaultFps", &ov.DefaultFps)
return cleared
}