Ctrl K
ComunidadGuías…

internal/autogen/flagtable.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
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
}