rebuild-config.sh
Rama main · réplica de lectura del repositorio original.
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=/dev/null
. "$SCRIPT_DIR/lib/bootstrap.sh" 2>/dev/null ||
. "$SCRIPT_DIR/../lib/bootstrap.sh" 2>/dev/null ||
{ echo "Error: missing LocalAI bootstrap library: lib/bootstrap.sh" >&2; exit 1; }
AI_DIR="$(resolve_ai_dir)"
CONF_DIR="$AI_DIR/$LOCALAI_CONF_SUBDIR"
CONFIG="${1:-$CONF_DIR/$LOCALAI_CONFIG_FILE}"
MODELS_DIR="$AI_DIR/$LOCALAI_MODELS_SUBDIR"
OVERRIDES_DIR="$CONF_DIR/${MODELS_OVERRIDE_SUBDIR:-$LOCALAI_MODELS_OVERRIDE_SUBDIR}"
# KEYS_DIR is always the real install location (it's what llama-swap's
# --config-dir points at and must exist regardless of which file $2 below
# targets); KEYS_FILE is the write target and defaults to the live file but
# can be pointed at a candidate/temp path the same way $CONFIG/$1 can.
KEYS_DIR="$CONF_DIR/${LOCALAI_KEYS_SUBDIR:-keys.d}"
KEYS_FILE="${2:-$KEYS_DIR/${LOCALAI_KEYS_FILE:-keys.yaml}}"
BIN="$AI_DIR/$LOCALAI_BIN_SUBDIR/llama-server"
CTX_SIZE="${CTX_SIZE:-$LOCALAI_CTX_SIZE}"
N_GPU_LAYERS="${N_GPU_LAYERS:-$LOCALAI_N_GPU_LAYERS}"
THREADS="${THREADS:-$LOCALAI_THREADS}"
CACHE_TYPE_K="${CACHE_TYPE_K:-$LOCALAI_CACHE_TYPE_K}"
CACHE_TYPE_V="${CACHE_TYPE_V:-$LOCALAI_CACHE_TYPE_V}"
PARALLEL="${PARALLEL:-$LOCALAI_PARALLEL}"
BATCH_SIZE="${BATCH_SIZE:-$LOCALAI_BATCH_SIZE}"
UBATCH_SIZE="${UBATCH_SIZE:-$LOCALAI_UBATCH_SIZE}"
FLASH_ATTN="${FLASH_ATTN:-$LOCALAI_FLASH_ATTN}"
JINJA="${JINJA:-$LOCALAI_JINJA}"
MLOCK="${MLOCK:-$LOCALAI_MLOCK}"
NO_MMAP="${NO_MMAP:-$LOCALAI_NO_MMAP}"
EXTRA_LLAMA_ARGS="${EXTRA_LLAMA_ARGS:-$LOCALAI_EXTRA_LLAMA_ARGS}"
MTP_MODELS_DIR="${MTP_MODELS_DIR:-$LOCALAI_MTP_MODELS_DIR}"
SPLIT_MODE="${SPLIT_MODE:-$LOCALAI_SPLIT_MODE}"
TENSOR_SPLIT="${TENSOR_SPLIT:-$LOCALAI_TENSOR_SPLIT}"
MAIN_GPU="${MAIN_GPU:-$LOCALAI_MAIN_GPU}"
DEVICE="${DEVICE:-$LOCALAI_DEVICE}"
SPEC_TYPE="${SPEC_TYPE:-$LOCALAI_SPEC_TYPE}"
SPEC_DRAFT_SAMPLING="${SPEC_DRAFT_SAMPLING:-$LOCALAI_SPEC_DRAFT_SAMPLING}"
SPEC_DRAFT_N_MAX="${SPEC_DRAFT_N_MAX:-$LOCALAI_SPEC_DRAFT_N_MAX}"
CPU_MOE="${CPU_MOE:-$LOCALAI_CPU_MOE}"
N_CPU_MOE="${N_CPU_MOE:-$LOCALAI_N_CPU_MOE}"
MOE_CACHE_MIB="${MOE_CACHE_MIB:-${LOCALAI_MOE_CACHE_MIB:-0}}"
DISABLE_KEEP_ALIVES="${DISABLE_KEEP_ALIVES:-${LOCALAI_DISABLE_KEEP_ALIVES:-0}}"
SPEC_DRAFT_CACHE_TYPE_K="${SPEC_DRAFT_CACHE_TYPE_K:-$LOCALAI_SPEC_DRAFT_CACHE_TYPE_K}"
SPEC_DRAFT_CACHE_TYPE_V="${SPEC_DRAFT_CACHE_TYPE_V:-$LOCALAI_SPEC_DRAFT_CACHE_TYPE_V}"
SPEC_DRAFT_CPU_MOE="${SPEC_DRAFT_CPU_MOE:-$LOCALAI_SPEC_DRAFT_CPU_MOE}"
SPEC_DRAFT_N_CPU_MOE="${SPEC_DRAFT_N_CPU_MOE:-$LOCALAI_SPEC_DRAFT_N_CPU_MOE}"
REASONING="${REASONING:-$LOCALAI_REASONING}"
REASONING_BUDGET="${REASONING_BUDGET:-$LOCALAI_REASONING_BUDGET}"
REASONING_FORMAT="${REASONING_FORMAT:-$LOCALAI_REASONING_FORMAT}"
REASONING_PRESERVE="${REASONING_PRESERVE:-$LOCALAI_REASONING_PRESERVE}"
AUTO_TUNE="${AUTO_TUNE:-$LOCALAI_AUTO_TUNE}"
METRICS_ENABLED="${METRICS_ENABLED:-$LOCALAI_METRICS_ENABLED}"
PRELOAD_MODELS="${PRELOAD_MODELS:-$LOCALAI_PRELOAD_MODELS}"
EMBEDDING_TTL="${EMBEDDING_TTL:-$LOCALAI_EMBEDDING_TTL}"
MAX_REQUEST_BODY_MB="${MAX_REQUEST_BODY_MB:-${LOCALAI_MAX_REQUEST_BODY_MB:-}}"
require_swap_version() {
local setting="$1" minimum="$2" version
version="$(llama_swap_version "$AI_DIR/$LOCALAI_BIN_SUBDIR/llama-swap")"
if ! [[ "$version" =~ ^[0-9]+$ ]] || ((10#$version < minimum)); then
echo "Error: $setting requires llama-swap v$minimum or newer. Run localai update or clear $setting in your configuration." >&2
exit 1
fi
}
# Blank keeps the upstream default and remains compatible with older swaps.
# Bound the value before arithmetic/YAML conversion to prevent integer overflow.
if [ -n "$MAX_REQUEST_BODY_MB" ]; then
if ! [[ "$MAX_REQUEST_BODY_MB" =~ ^[1-9][0-9]{0,9}$ ]] || ((MAX_REQUEST_BODY_MB > 2147483647)); then
echo "Error: MAX_REQUEST_BODY_MB must be a whole number from 1 to 2147483647, without leading zeros, or blank for the default." >&2
exit 1
fi
require_swap_version MAX_REQUEST_BODY_MB 266
fi
validate_spec_draft_sampling() {
case "$2" in
""|greedy|probabilistic) ;;
*) echo "Error: $1 must be greedy or probabilistic when set." >&2; exit 1 ;;
esac
}
validate_optional_positive_integer() {
local name="$1"
local value="$2"
[ -z "$value" ] && return 0
if ! [[ "$value" =~ ^[0-9]+$ ]] || ((value < 1)); then
echo "Error: $name must be a positive integer when set." >&2
exit 1
fi
}
validate_optional_nonnegative_integer() {
local name="$1"
local value="$2"
[ -z "$value" ] && return 0
if ! [[ "$value" =~ ^[0-9]+$ ]]; then
echo "Error: $name must be a non-negative integer when set." >&2
exit 1
fi
}
validate_bool() {
local name="$1"
local value="$2"
case "$value" in
0|1) ;;
*)
echo "Error: $name must be 0 or 1." >&2
exit 1
;;
esac
}
# validate_optional_number_range NAME VALUE MIN MAX
validate_optional_number_range() {
local name="$1"
local value="$2"
local min="$3"
local max="$4"
[ -z "$value" ] && return 0
if ! [[ "$value" =~ ^[0-9]+(\.[0-9]+)?$ ]] ||
! awk -v value="$value" -v min="$min" -v max="$max" \
'BEGIN { exit !(value + 0 >= min + 0 && value + 0 <= max + 0) }'; then
echo "Error: $name must be a number from $min to $max when set." >&2
exit 1
fi
}
validate_listen_host() {
local value="$1"
if [ -z "$value" ] || ! [[ "$value" =~ ^[][A-Za-z0-9._:-]+$ ]]; then
echo "Error: LOCALAI_LISTEN_HOST must be an address or hostname without spaces, quotes, or path characters." >&2
exit 1
fi
}
validate_preload_models() {
local value="$1"
local id
local -a ids=()
[ -z "$value" ] && return 0
case "$value" in
*$'\n'*|*$'\r'*)
echo "Error: LOCALAI_PRELOAD_MODELS must be a single line." >&2
exit 1
;;
esac
IFS=', ' read -r -a ids <<<"$value"
for id in "${ids[@]}"; do
[ -n "$id" ] || continue
case "$id" in
*\"*|*"'"*|*'`'*|*\\*|*$'\n'*|*$'\r'*)
echo "Error: LOCALAI_PRELOAD_MODELS contains an unsupported model id: $id" >&2
exit 1
;;
esac
done
}
validate_spec_type() {
local name="$1"
local value="$2"
case "$value" in
""|none|draft-simple|draft-eagle3|draft-mtp|draft-dflash|draft-dspark|ngram-simple|ngram-map-k|ngram-map-k4v|ngram-mod|ngram-cache) ;;
*)
echo "Error: $name has an unsupported value: $value" >&2
exit 1
;;
esac
}
validate_reasoning() {
local name="$1"
local value="$2"
case "$value" in
""|on|off|auto) ;;
*)
echo "Error: $name must be '', 'on', 'off', or 'auto'." >&2
exit 1
;;
esac
}
validate_reasoning_format() {
local name="$1"
local value="$2"
case "$value" in
""|none|deepseek|deepseek-legacy) ;;
*)
echo "Error: $name must be '', 'none', 'deepseek', or 'deepseek-legacy'." >&2
exit 1
;;
esac
}
validate_reasoning_budget() {
local name="$1"
local value="$2"
[ -z "$value" ] && return 0
if ! [[ "$value" =~ ^-?[0-9]+$ ]] || ((value < -1)); then
echo "Error: $name must be -1 or a non-negative integer when set." >&2
exit 1
fi
}
validate_split_mode() {
local name="$1"
local value="$2"
case "$value" in
""|none|layer|tensor) ;;
*)
echo "Error: $name must be 'none', 'layer', or 'tensor'." >&2
exit 1
;;
esac
}
validate_tensor_split() {
local name="$1"
local value="$2"
[ -z "$value" ] && return 0
if ! [[ "$value" =~ ^[0-9]+(\.[0-9]+)?(,[0-9]+(\.[0-9]+)?)*$ ]]; then
echo "Error: $name must be a comma-separated list of numbers, e.g. '3,1'." >&2
exit 1
fi
}
validate_single_line() {
local name="$1"
local value="$2"
case "$value" in
*$'\n'*|*$'\r'*)
echo "Error: $name must not contain newlines." >&2
exit 1
;;
esac
}
validate_shell_safe() {
local name="$1"
local value="$2"
case "$value" in
*\"*|*"'"*|*'`'*|*\\*)
echo "Error: $name must not contain quotes or backslashes." >&2
exit 1
;;
esac
}
trim_ws() {
local value="$1"
value="${value#"${value%%[![:space:]]*}"}"
value="${value%"${value##*[![:space:]]}"}"
printf '%s' "$value"
}
if ! [[ "$CTX_SIZE" =~ ^[0-9]+$ ]] || ((CTX_SIZE < 1)); then
echo "Error: CTX_SIZE must be a positive integer." >&2
exit 1
fi
if ! [[ "$N_GPU_LAYERS" =~ ^([0-9]+|auto|all)$ ]]; then
echo "Error: N_GPU_LAYERS must be a non-negative integer, 'auto', or 'all'." >&2
exit 1
fi
if ! [[ "$THREADS" =~ ^[0-9]+$ ]]; then
echo "Error: THREADS must be a non-negative integer." >&2
exit 1
fi
validate_optional_positive_integer PARALLEL "$PARALLEL"
validate_optional_positive_integer BATCH_SIZE "$BATCH_SIZE"
validate_optional_positive_integer UBATCH_SIZE "$UBATCH_SIZE"
validate_optional_positive_integer SPEC_DRAFT_N_MAX "$SPEC_DRAFT_N_MAX"
validate_optional_nonnegative_integer EMBEDDING_TTL "$EMBEDDING_TTL"
validate_optional_nonnegative_integer MAIN_GPU "$MAIN_GPU"
validate_optional_nonnegative_integer N_CPU_MOE "$N_CPU_MOE"
validate_optional_nonnegative_integer SPEC_DRAFT_N_CPU_MOE "$SPEC_DRAFT_N_CPU_MOE"
validate_bool SPEC_DRAFT_CPU_MOE "$SPEC_DRAFT_CPU_MOE"
validate_single_line SPEC_DRAFT_CACHE_TYPE_K "$SPEC_DRAFT_CACHE_TYPE_K"
validate_single_line SPEC_DRAFT_CACHE_TYPE_V "$SPEC_DRAFT_CACHE_TYPE_V"
validate_bool FLASH_ATTN "$FLASH_ATTN"
validate_bool JINJA "$JINJA"
validate_bool MLOCK "$MLOCK"
validate_bool NO_MMAP "$NO_MMAP"
validate_single_line MTP_MODELS_DIR "$MTP_MODELS_DIR"
validate_bool AUTO_TUNE "$AUTO_TUNE"
validate_bool METRICS_ENABLED "$METRICS_ENABLED"
validate_bool CPU_MOE "$CPU_MOE"
validate_bool DISABLE_KEEP_ALIVES "$DISABLE_KEEP_ALIVES"
validate_optional_nonnegative_integer MOE_CACHE_MIB "$MOE_CACHE_MIB"
validate_bool REASONING_PRESERVE "$REASONING_PRESERVE"
validate_spec_type SPEC_TYPE "$SPEC_TYPE"
validate_spec_draft_sampling SPEC_DRAFT_SAMPLING "$SPEC_DRAFT_SAMPLING"
validate_split_mode SPLIT_MODE "$SPLIT_MODE"
validate_tensor_split TENSOR_SPLIT "$TENSOR_SPLIT"
validate_shell_safe DEVICE "$DEVICE"
validate_reasoning REASONING "$REASONING"
validate_reasoning_format REASONING_FORMAT "$REASONING_FORMAT"
validate_reasoning_budget REASONING_BUDGET "$REASONING_BUDGET"
validate_optional_nonnegative_integer LOCALAI_HEALTH_CHECK_TIMEOUT "$LOCALAI_HEALTH_CHECK_TIMEOUT"
validate_optional_nonnegative_integer LOCALAI_GLOBAL_TTL "$LOCALAI_GLOBAL_TTL"
validate_listen_host "$LOCALAI_LISTEN_HOST"
validate_preload_models "$PRELOAD_MODELS"
case "$EXTRA_LLAMA_ARGS" in
*$'\n'*|*$'\r'*)
echo "Error: EXTRA_LLAMA_ARGS must be a single line." >&2
exit 1
;;
esac
mkdir -p "$CONF_DIR" "$MODELS_DIR" "$OVERRIDES_DIR" "$KEYS_DIR"
# API_KEY_REGISTRY is validated before anything is written to $CONFIG, so a
# corrupted registry aborts the rebuild instead of silently generating an
# unauthenticated config over a previously-authenticated one.
API_KEY_REGISTRY="$CONF_DIR/${LOCALAI_API_KEY_FILE:-api-keys.tsv}"
REQUIRE_API_KEY="${LOCALAI_REQUIRE_API_KEY:-0}"
validate_bool REQUIRE_API_KEY "$REQUIRE_API_KEY"
if ! api_key_registry_validate "$API_KEY_REGISTRY"; then
echo "Refusing to rebuild configuration; $CONFIG was left unchanged." >&2
exit 1
fi
ACTIVE_API_KEYS="$(api_key_active_named_secrets "$API_KEY_REGISTRY")"
ACTIVE_API_KEY_COUNT=0
[ -z "$ACTIVE_API_KEYS" ] || ACTIVE_API_KEY_COUNT="$(grep -c . <<<"$ACTIVE_API_KEYS")"
if [ "$REQUIRE_API_KEY" = "1" ] && [ "$ACTIVE_API_KEY_COUNT" -eq 0 ]; then
echo "Error: LOCALAI_REQUIRE_API_KEY=1 but no active API keys exist." >&2
echo "Create one first: localai key create" >&2
exit 1
fi
BACKEND="$([ -f "$CONF_DIR/$LOCALAI_BACKEND_FILE" ] && cat "$CONF_DIR/$LOCALAI_BACKEND_FILE" || printf '%s' "$LOCALAI_DEFAULT_BACKEND")"
RAM_BYTES="$(system_ram_bytes)"
VRAM_BYTES="$(gpu_vram_bytes)"
# A pinned/older LLAMA_CPP_VERSION may predate '-ngl auto' support; detect it
# once so auto-tune falls back to the configured N_GPU_LAYERS instead of
# passing a flag the installed llama-server would reject.
NGL_AUTO_SUPPORTED=0
llama_server_supports_ngl_auto "$BIN" && NGL_AUTO_SUPPORTED=1
if [ "$AUTO_TUNE" = "1" ] && [ "$BACKEND" != "cpu" ] && [ "$NGL_AUTO_SUPPORTED" -eq 0 ]; then
echo "Note: installed llama-server does not support '-ngl auto'; auto-tune will use N_GPU_LAYERS=$N_GPU_LAYERS for every model instead of per-model fitting." >&2
fi
# Snapshot the global defaults once; the per-model loop resets its working
# copies from these on every iteration so a models.d override or an
# auto-tune result for one model never leaks into the next.
GLOBAL_THREADS="$THREADS"
GLOBAL_PARALLEL="$PARALLEL"
GLOBAL_BATCH_SIZE="$BATCH_SIZE"
GLOBAL_UBATCH_SIZE="$UBATCH_SIZE"
GLOBAL_JINJA="$JINJA"
GLOBAL_MLOCK="$MLOCK"
GLOBAL_NO_MMAP="$NO_MMAP"
GLOBAL_EXTRA_LLAMA_ARGS="$EXTRA_LLAMA_ARGS"
GLOBAL_MTP_MODELS_DIR="$MTP_MODELS_DIR"
GLOBAL_CTX_SIZE="$CTX_SIZE"
GLOBAL_N_GPU_LAYERS="$N_GPU_LAYERS"
GLOBAL_CACHE_TYPE_K="$CACHE_TYPE_K"
GLOBAL_CACHE_TYPE_V="$CACHE_TYPE_V"
GLOBAL_FLASH_ATTN="$FLASH_ATTN"
GLOBAL_SPEC_TYPE="$SPEC_TYPE"
GLOBAL_SPEC_DRAFT_SAMPLING="$SPEC_DRAFT_SAMPLING"
GLOBAL_SPEC_DRAFT_N_MAX="$SPEC_DRAFT_N_MAX"
GLOBAL_SPLIT_MODE="$SPLIT_MODE"
GLOBAL_TENSOR_SPLIT="$TENSOR_SPLIT"
GLOBAL_MAIN_GPU="$MAIN_GPU"
GLOBAL_DEVICE="$DEVICE"
GLOBAL_MOE_CACHE_MIB="$MOE_CACHE_MIB"
GLOBAL_DISABLE_KEEP_ALIVES="$DISABLE_KEEP_ALIVES"
GLOBAL_CPU_MOE="$CPU_MOE"
GLOBAL_N_CPU_MOE="$N_CPU_MOE"
GLOBAL_SPEC_DRAFT_CACHE_TYPE_K="$SPEC_DRAFT_CACHE_TYPE_K"
GLOBAL_SPEC_DRAFT_CACHE_TYPE_V="$SPEC_DRAFT_CACHE_TYPE_V"
GLOBAL_SPEC_DRAFT_CPU_MOE="$SPEC_DRAFT_CPU_MOE"
GLOBAL_SPEC_DRAFT_N_CPU_MOE="$SPEC_DRAFT_N_CPU_MOE"
GLOBAL_REASONING="$REASONING"
GLOBAL_REASONING_BUDGET="$REASONING_BUDGET"
GLOBAL_REASONING_FORMAT="$REASONING_FORMAT"
GLOBAL_REASONING_PRESERVE="$REASONING_PRESERVE"
# Build candidates beside their destinations so failed validation cannot
# truncate the working configuration, and each final rename stays atomic.
MODEL_ENTRIES="$(localai_model_entries "$MODELS_DIR")"
CONFIG_DEST="$CONFIG"
KEYS_DEST="$KEYS_FILE"
CONFIG="$(mktemp "${CONFIG_DEST}.XXXXXX")"
KEYS_FILE=""
CONFIG_BACKUP=""
KEYS_BACKUP=""
trap 'rm -f -- "$CONFIG" "${KEYS_FILE:-}" "${CONFIG_BACKUP:-}" "${KEYS_BACKUP:-}"' EXIT
KEYS_FILE="$(mktemp "${KEYS_DEST}.XXXXXX")"
cat > "$CONFIG" <<CFG
healthCheckTimeout: $LOCALAI_HEALTH_CHECK_TIMEOUT
globalTTL: $LOCALAI_GLOBAL_TTL
CFG
chmod 600 "$CONFIG" 2>/dev/null || true
if [ -n "$MAX_REQUEST_BODY_MB" ]; then
printf 'maxRequestBodyMB: %s\n' "$MAX_REQUEST_BODY_MB" >> "$CONFIG"
fi
# Active keys are rendered into their own file (KEYS_FILE) and merged into
# the running config by llama-swap's --config-dir, rather than embedded in
# $CONFIG, so config.yaml (models/tuning) and plaintext secrets stay in
# separate files. Removed entirely -- not left empty -- when there are no
# active keys, so a stale keys file can never be merged in by mistake.
if [ "$ACTIVE_API_KEY_COUNT" -gt 0 ]; then
{
echo "apiKeys:"
while IFS=$'\t' read -r ACTIVE_NAME ACTIVE_KEY; do
[ -n "$ACTIVE_KEY" ] || continue
printf ' # %s\n - "%s"\n' "$ACTIVE_NAME" "$ACTIVE_KEY"
done <<<"$ACTIVE_API_KEYS"
} > "$KEYS_FILE"
chmod 600 "$KEYS_FILE" 2>/dev/null || true
else
rm -f "$KEYS_FILE"
fi
# llama-swap's performance block controls its system/GPU monitoring stats (what
# the /metrics endpoint and the UI's Performance view serve). Upstream's key is
# `disabled` (default false = collecting); there is no `enable` key. This block
# used to emit `enable: true`, which llama-swap silently ignores (and its
# validator still accepts, so it never fails loudly) -- meaning METRICS_ENABLED=0
# could never actually turn the stats off. Emit the real key both ways instead.
if [ "$METRICS_ENABLED" = "1" ]; then
cat >> "$CONFIG" <<CFG
performance:
disabled: false
CFG
else
cat >> "$CONFIG" <<CFG
performance:
disabled: true
CFG
fi
if [ -n "$PRELOAD_MODELS" ]; then
{
echo
echo "hooks:"
echo " on_startup:"
echo " preload:"
for PRELOAD_ID in ${PRELOAD_MODELS//,/ }; do
printf ' - "%s"\n' "$PRELOAD_ID"
done
} >> "$CONFIG"
fi
cat >> "$CONFIG" <<CFG
models:
CFG
MODEL_COUNT=0
while IFS=$'\t' read -r NAME MODEL_REL MODEL; do
[ -n "$NAME" ] || continue
case "$NAME$MODEL_REL" in
*\"*|*"'"*|*'`'*|*\\*)
echo "Skipping unsupported model path: $MODEL_REL" >&2
continue
;;
esac
IS_EMBEDDING=0
MODEL_TYPE=chat
POOLING=""
MODEL_EXTRA_ARGS=""
if [[ "${NAME,,}" == *qwen3*embedding* ]]; then
MODEL_EXTRA_ARGS="--embeddings --pooling last"
IS_EMBEDDING=1
MODEL_TYPE=embedding
POOLING=last
elif model_is_embedding_name "$NAME"; then
MODEL_EXTRA_ARGS="--embeddings"
IS_EMBEDDING=1
MODEL_TYPE=embedding
fi
if [[ "${NAME,,}" == *rerank* ]]; then MODEL_TYPE=reranking; fi
# Reset per-model working values to the global defaults before auto-tune
# and models.d overrides are applied.
THREADS="$GLOBAL_THREADS"
PARALLEL="$GLOBAL_PARALLEL"
BATCH_SIZE="$GLOBAL_BATCH_SIZE"
UBATCH_SIZE="$GLOBAL_UBATCH_SIZE"
JINJA="$GLOBAL_JINJA"
MLOCK="$GLOBAL_MLOCK"
NO_MMAP="$GLOBAL_NO_MMAP"
EXTRA_LLAMA_ARGS="$GLOBAL_EXTRA_LLAMA_ARGS"
MTP_MODELS_DIR="$GLOBAL_MTP_MODELS_DIR"
CTX_SIZE="$GLOBAL_CTX_SIZE"
N_GPU_LAYERS="$GLOBAL_N_GPU_LAYERS"
CACHE_TYPE_K="$GLOBAL_CACHE_TYPE_K"
CACHE_TYPE_V="$GLOBAL_CACHE_TYPE_V"
FLASH_ATTN="$GLOBAL_FLASH_ATTN"
SPEC_TYPE="$GLOBAL_SPEC_TYPE"
SPEC_DRAFT_SAMPLING="$GLOBAL_SPEC_DRAFT_SAMPLING"
SPEC_DRAFT_N_MAX="$GLOBAL_SPEC_DRAFT_N_MAX"
SPLIT_MODE="$GLOBAL_SPLIT_MODE"
TENSOR_SPLIT="$GLOBAL_TENSOR_SPLIT"
MAIN_GPU="$GLOBAL_MAIN_GPU"
DEVICE="$GLOBAL_DEVICE"
MOE_CACHE_MIB="$GLOBAL_MOE_CACHE_MIB"
DISABLE_KEEP_ALIVES="$GLOBAL_DISABLE_KEEP_ALIVES"
CPU_MOE="$GLOBAL_CPU_MOE"
N_CPU_MOE="$GLOBAL_N_CPU_MOE"
SPEC_DRAFT_CACHE_TYPE_K="$GLOBAL_SPEC_DRAFT_CACHE_TYPE_K"
SPEC_DRAFT_CACHE_TYPE_V="$GLOBAL_SPEC_DRAFT_CACHE_TYPE_V"
SPEC_DRAFT_CPU_MOE="$GLOBAL_SPEC_DRAFT_CPU_MOE"
SPEC_DRAFT_N_CPU_MOE="$GLOBAL_SPEC_DRAFT_N_CPU_MOE"
REASONING="$GLOBAL_REASONING"
REASONING_BUDGET="$GLOBAL_REASONING_BUDGET"
REASONING_FORMAT="$GLOBAL_REASONING_FORMAT"
REASONING_PRESERVE="$GLOBAL_REASONING_PRESERVE"
TTL=""
ALIASES=""
MMPROJ=""
MMPROJ_URL=""
MMPROJ_OFFLOAD="0"
IMAGE_MIN_TOKENS=""
IMAGE_MAX_TOKENS=""
OVERRIDE_TENSOR=""
SPEC_DRAFT_MODEL=""
SPEC_DRAFT_N_MIN=""
SPEC_DRAFT_DEVICE=""
SPEC_DRAFT_NGL=""
LORA=""
LORA_SCALED=""
SET_TEMPERATURE=""
SET_TOP_P=""
# unset (not just reassigned) so a models.d override that declared this as
# an array on a previous loop iteration can't leak leftover elements into
# a model that doesn't override it -- `EXTRA_ARGS=""` alone only replaces
# index 0 of an existing array, it doesn't clear the rest.
unset -v EXTRA_ARGS
EXTRA_ARGS=""
if [ "$AUTO_TUNE" = "1" ] && [ "$BACKEND" != "cpu" ]; then
MODEL_BYTES="$(localai_model_bytes "$MODEL")"
# Auto-tune now applies the VRAM-aware context recommendation too, so the
# value `localai suggest` prints is the value that actually reaches
# llama-server. models.d can still override it per model.
IFS=$'\t' read -r AUTO_CTX AUTO_NGL AUTO_FLASH AUTO_CACHE_K AUTO_CACHE_V \
< <(compute_model_runtime_defaults "$MODEL_BYTES" "$RAM_BYTES" "$VRAM_BYTES" "$BACKEND" "$NGL_AUTO_SUPPORTED")
if [[ "$AUTO_CTX" =~ ^[0-9]+$ ]] && ((AUTO_CTX > 0)); then
CTX_SIZE="$AUTO_CTX"
fi
[ "$AUTO_NGL" = "-" ] || N_GPU_LAYERS="$AUTO_NGL"
FLASH_ATTN="$AUTO_FLASH"
CACHE_TYPE_K="$AUTO_CACHE_K"
CACHE_TYPE_V="$AUTO_CACHE_V"
fi
if [ "$IS_EMBEDDING" -eq 1 ]; then
TTL="$EMBEDDING_TTL"
SPEC_TYPE=""
fi
# Auto-detect a multimodal projector placed alongside a model kept in its
# own folder, e.g. models/gemma-4-vision/mmproj-gemma-4.gguf. Only applies
# to the documented per-model-folder layout; flat top-level models need an
# explicit MMPROJ= in a models.d override.
MODEL_DIR_REL="${MODEL_REL%/*}"
if [ "$MODEL_DIR_REL" != "$MODEL_REL" ]; then
MMPROJ_CANDIDATE="$(find "$MODELS_DIR/$MODEL_DIR_REL" -maxdepth 1 -type f -iname 'mmproj*.gguf' -print -quit 2>/dev/null || true)"
if [ -n "$MMPROJ_CANDIDATE" ]; then
case "$MMPROJ_CANDIDATE" in
*\"*|*"'"*|*'`'*|*\\*)
echo "Warning: skipping mmproj file with unsupported path: $MMPROJ_CANDIDATE" >&2
;;
*)
MMPROJ="$MMPROJ_CANDIDATE"
;;
esac
fi
fi
# Per-model override: conf/models.d/<model-id>.conf. Sourced last so it can
# override auto-tune, mmproj detection, or any global default for this one
# model. Uses the same variable names as the global config above, plus
# TTL, ALIASES, MMPROJ, SET_TEMPERATURE, SET_TOP_P, EXTRA_ARGS, SPLIT_MODE,
# TENSOR_SPLIT, MAIN_GPU, DEVICE, CPU_MOE, N_CPU_MOE, MOE_CACHE_MIB,
# DISABLE_KEEP_ALIVES, OVERRIDE_TENSOR,
# REASONING, REASONING_BUDGET, REASONING_FORMAT, REASONING_PRESERVE,
# SPEC_DRAFT_MODEL, SPEC_DRAFT_N_MIN, SPEC_DRAFT_DEVICE, SPEC_DRAFT_NGL,
# SPEC_DRAFT_CACHE_TYPE_K, SPEC_DRAFT_CACHE_TYPE_V, SPEC_DRAFT_CPU_MOE,
# SPEC_DRAFT_N_CPU_MOE, MMPROJ_URL, MMPROJ_OFFLOAD, IMAGE_MIN_TOKENS,
# IMAGE_MAX_TOKENS, LORA, and LORA_SCALED. THREADS, PARALLEL, BATCH_SIZE,
# UBATCH_SIZE, JINJA, MLOCK, NO_MMAP, EXTRA_LLAMA_ARGS, and MTP_MODELS_DIR
# also apply independently to each model.
OVERRIDE_FILE="$OVERRIDES_DIR/$NAME.conf"
if [ -f "$OVERRIDE_FILE" ]; then
# shellcheck disable=SC1090
. "$OVERRIDE_FILE"
fi
# Explicit type and pooling support arbitrarily named compatible GGUFs.
case "$MODEL_TYPE" in
chat|completion) MODEL_EXTRA_ARGS="" ;;
embedding) MODEL_EXTRA_ARGS="--embeddings"; SPEC_TYPE=""; TTL="${TTL:-$EMBEDDING_TTL}" ;;
reranking) MODEL_EXTRA_ARGS="--reranking"; SPEC_TYPE=""; TTL="${TTL:-$EMBEDDING_TTL}" ;;
*) echo "Error: MODEL_TYPE for model $NAME must be chat, completion, embedding, or reranking." >&2; exit 1 ;;
esac
case "$POOLING" in
""|none|mean|cls|last|rank) ;;
*) echo "Error: invalid POOLING for model $NAME." >&2; exit 1 ;;
esac
[ -z "$POOLING" ] || MODEL_EXTRA_ARGS="$MODEL_EXTRA_ARGS --pooling $POOLING"
# EXTRA_ARGS reaches the model's cmd: line as a string llama-swap re-parses
# with its own shell-like tokenizer, so a value containing quotes/braces
# (e.g. --chat-template-kwargs JSON) needs to survive that second parse.
# Plain-string EXTRA_ARGS keeps the historical behavior (spliced in as-is,
# relying on llama-swap to split it on whitespace) so existing overrides
# are unaffected. Declaring EXTRA_ARGS as a bash array instead lets each
# element carry spaces/quotes safely -- every element is single-quoted
# here so it always arrives as exactly one argv token, regardless of what
# llama-swap's tokenizer would otherwise do with the raw characters.
EXTRA_ARGS_RENDERED=""
if [[ "$(declare -p EXTRA_ARGS 2>/dev/null)" == "declare -a"* ]]; then
for EXTRA_ARGS_ELEMENT in "${EXTRA_ARGS[@]}"; do
[ -n "$EXTRA_ARGS_ELEMENT" ] || continue
EXTRA_ARGS_RENDERED="$EXTRA_ARGS_RENDERED
$(shell_quote_token "$EXTRA_ARGS_ELEMENT")"
done
else
[ -z "$EXTRA_ARGS" ] || EXTRA_ARGS_RENDERED="
$EXTRA_ARGS"
fi
if ! [[ "$CTX_SIZE" =~ ^[0-9]+$ ]] || ((CTX_SIZE < 1)); then
echo "Error: CTX_SIZE for model $NAME must be a positive integer." >&2
exit 1
fi
if ! [[ "$N_GPU_LAYERS" =~ ^([0-9]+|auto|all)$ ]]; then
echo "Error: N_GPU_LAYERS for model $NAME must be a non-negative integer, 'auto', or 'all'." >&2
exit 1
fi
validate_bool "FLASH_ATTN (model $NAME)" "$FLASH_ATTN"
validate_spec_type "SPEC_TYPE (model $NAME)" "$SPEC_TYPE"
validate_spec_draft_sampling "SPEC_DRAFT_SAMPLING (model $NAME)" "$SPEC_DRAFT_SAMPLING"
validate_optional_positive_integer "SPEC_DRAFT_N_MAX (model $NAME)" "$SPEC_DRAFT_N_MAX"
validate_optional_nonnegative_integer "TTL (model $NAME)" "$TTL"
validate_split_mode "SPLIT_MODE (model $NAME)" "$SPLIT_MODE"
validate_tensor_split "TENSOR_SPLIT (model $NAME)" "$TENSOR_SPLIT"
validate_optional_nonnegative_integer "MAIN_GPU (model $NAME)" "$MAIN_GPU"
validate_shell_safe "DEVICE (model $NAME)" "$DEVICE"
validate_bool "DISABLE_KEEP_ALIVES (model $NAME)" "$DISABLE_KEEP_ALIVES"
validate_optional_nonnegative_integer "MOE_CACHE_MIB (model $NAME)" "$MOE_CACHE_MIB"
validate_bool "CPU_MOE (model $NAME)" "$CPU_MOE"
validate_optional_nonnegative_integer "N_CPU_MOE (model $NAME)" "$N_CPU_MOE"
validate_single_line "OVERRIDE_TENSOR (model $NAME)" "$OVERRIDE_TENSOR"
validate_reasoning "REASONING (model $NAME)" "$REASONING"
validate_reasoning_format "REASONING_FORMAT (model $NAME)" "$REASONING_FORMAT"
validate_reasoning_budget "REASONING_BUDGET (model $NAME)" "$REASONING_BUDGET"
validate_bool "REASONING_PRESERVE (model $NAME)" "$REASONING_PRESERVE"
validate_single_line "SPEC_DRAFT_MODEL (model $NAME)" "$SPEC_DRAFT_MODEL"
validate_optional_nonnegative_integer "SPEC_DRAFT_N_MIN (model $NAME)" "$SPEC_DRAFT_N_MIN"
validate_single_line "SPEC_DRAFT_DEVICE (model $NAME)" "$SPEC_DRAFT_DEVICE"
validate_optional_nonnegative_integer "SPEC_DRAFT_NGL (model $NAME)" "$SPEC_DRAFT_NGL"
validate_optional_nonnegative_integer "SPEC_DRAFT_N_CPU_MOE (model $NAME)" "$SPEC_DRAFT_N_CPU_MOE"
validate_bool "SPEC_DRAFT_CPU_MOE (model $NAME)" "$SPEC_DRAFT_CPU_MOE"
validate_single_line "SPEC_DRAFT_CACHE_TYPE_K (model $NAME)" "$SPEC_DRAFT_CACHE_TYPE_K"
validate_single_line "SPEC_DRAFT_CACHE_TYPE_V (model $NAME)" "$SPEC_DRAFT_CACHE_TYPE_V"
validate_single_line "MMPROJ_URL (model $NAME)" "$MMPROJ_URL"
validate_bool "MMPROJ_OFFLOAD (model $NAME)" "$MMPROJ_OFFLOAD"
validate_optional_positive_integer "IMAGE_MIN_TOKENS (model $NAME)" "$IMAGE_MIN_TOKENS"
validate_optional_positive_integer "IMAGE_MAX_TOKENS (model $NAME)" "$IMAGE_MAX_TOKENS"
validate_single_line "LORA (model $NAME)" "$LORA"
validate_single_line "LORA_SCALED (model $NAME)" "$LORA_SCALED"
validate_optional_number_range "SET_TEMPERATURE (model $NAME)" "$SET_TEMPERATURE" 0 2
validate_optional_number_range "SET_TOP_P (model $NAME)" "$SET_TOP_P" 0 1
if ! [[ "$THREADS" =~ ^[0-9]+$ ]]; then
echo "Error: THREADS for model $NAME must be a non-negative integer." >&2
exit 1
fi
validate_optional_positive_integer "PARALLEL (model $NAME)" "$PARALLEL"
validate_optional_positive_integer "BATCH_SIZE (model $NAME)" "$BATCH_SIZE"
validate_optional_positive_integer "UBATCH_SIZE (model $NAME)" "$UBATCH_SIZE"
validate_bool "JINJA (model $NAME)" "$JINJA"
validate_bool "MLOCK (model $NAME)" "$MLOCK"
validate_bool "NO_MMAP (model $NAME)" "$NO_MMAP"
validate_single_line "EXTRA_LLAMA_ARGS (model $NAME)" "$EXTRA_LLAMA_ARGS"
validate_single_line "MTP_MODELS_DIR (model $NAME)" "$MTP_MODELS_DIR"
# Build shared flags after overrides, independently for each model.
COMMON_EXTRA_ARGS=""
[ -z "$PARALLEL" ] || COMMON_EXTRA_ARGS="$COMMON_EXTRA_ARGS
--parallel $PARALLEL"
[ -z "$BATCH_SIZE" ] || COMMON_EXTRA_ARGS="$COMMON_EXTRA_ARGS
--batch-size $BATCH_SIZE"
[ -z "$UBATCH_SIZE" ] || COMMON_EXTRA_ARGS="$COMMON_EXTRA_ARGS
--ubatch-size $UBATCH_SIZE"
[ "$JINJA" = "0" ] || COMMON_EXTRA_ARGS="$COMMON_EXTRA_ARGS
--jinja"
# --mlock/--no-mmap are deprecated upstream in favor of --load-mode; map the
# two booleans onto it so generated configs don't trigger llama-server's
# deprecation warning. mmap is the llama-server default, so it's omitted.
if [ "$MLOCK" = "1" ] && [ "$NO_MMAP" = "1" ]; then
LOAD_MODE="mlock"
elif [ "$MLOCK" = "1" ]; then
LOAD_MODE="mmap+mlock"
elif [ "$NO_MMAP" = "1" ]; then
LOAD_MODE="none"
else
LOAD_MODE=""
fi
[ -z "$LOAD_MODE" ] || COMMON_EXTRA_ARGS="$COMMON_EXTRA_ARGS
--load-mode $LOAD_MODE"
[ -z "$EXTRA_LLAMA_ARGS" ] || COMMON_EXTRA_ARGS="$COMMON_EXTRA_ARGS
$EXTRA_LLAMA_ARGS"
[ -z "$MTP_MODELS_DIR" ] || COMMON_EXTRA_ARGS="$COMMON_EXTRA_ARGS
--models-dir $(shell_quote_token "$MTP_MODELS_DIR")"
MODEL_SPECIFIC_ARGS=""
[ "$FLASH_ATTN" = "0" ] || MODEL_SPECIFIC_ARGS="$MODEL_SPECIFIC_ARGS
--flash-attn on"
if [ -n "$MMPROJ" ]; then
MODEL_SPECIFIC_ARGS="$MODEL_SPECIFIC_ARGS
--mmproj \"$MMPROJ\""
fi
[ -z "$SPLIT_MODE" ] || MODEL_SPECIFIC_ARGS="$MODEL_SPECIFIC_ARGS
--split-mode $SPLIT_MODE"
[ -z "$TENSOR_SPLIT" ] || MODEL_SPECIFIC_ARGS="$MODEL_SPECIFIC_ARGS
--tensor-split $TENSOR_SPLIT"
[ -z "$MAIN_GPU" ] || MODEL_SPECIFIC_ARGS="$MODEL_SPECIFIC_ARGS
--main-gpu $MAIN_GPU"
if [ -n "$DEVICE" ]; then
MODEL_SPECIFIC_ARGS="$MODEL_SPECIFIC_ARGS
--device \"$DEVICE\""
fi
if [ -n "$SPEC_TYPE" ] && [ "$SPEC_TYPE" != "none" ]; then
MODEL_SPECIFIC_ARGS="$MODEL_SPECIFIC_ARGS
--spec-type $SPEC_TYPE
--spec-draft-n-max $SPEC_DRAFT_N_MAX"
fi
if [ -n "$SPEC_DRAFT_SAMPLING" ]; then
case "$SPEC_TYPE" in
draft-simple|draft-mtp)
if ! "$BIN" --help 2>&1 | grep -- '--spec-draft-sampling' >/dev/null; then
echo "Error: model $NAME requests SPEC_DRAFT_SAMPLING but llama-server does not support it; update llama.cpp or clear the setting." >&2
exit 1
fi
MODEL_SPECIFIC_ARGS="$MODEL_SPECIFIC_ARGS
--spec-draft-sampling $SPEC_DRAFT_SAMPLING"
;;
esac
fi
# --spec-draft-model supplies an actual smaller model for draft-* SPEC_TYPE
# values (draft-simple, draft-eagle3, ...), unlike the ngram-* self-spec
# variants above which need no separate model. Independent of SPEC_TYPE so
# a models.d file can set either or both.
if [ -n "$SPEC_DRAFT_MODEL" ]; then
MODEL_SPECIFIC_ARGS="$MODEL_SPECIFIC_ARGS
--spec-draft-model $(shell_quote_token "$SPEC_DRAFT_MODEL")"
[ -z "$SPEC_DRAFT_N_MIN" ] || MODEL_SPECIFIC_ARGS="$MODEL_SPECIFIC_ARGS
--spec-draft-n-min $SPEC_DRAFT_N_MIN"
[ -z "$SPEC_DRAFT_DEVICE" ] || MODEL_SPECIFIC_ARGS="$MODEL_SPECIFIC_ARGS
--spec-draft-device $(shell_quote_token "$SPEC_DRAFT_DEVICE")"
[ -z "$SPEC_DRAFT_NGL" ] || MODEL_SPECIFIC_ARGS="$MODEL_SPECIFIC_ARGS
--spec-draft-ngl $SPEC_DRAFT_NGL"
[ -z "$SPEC_DRAFT_CACHE_TYPE_K" ] || MODEL_SPECIFIC_ARGS="$MODEL_SPECIFIC_ARGS
--cache-type-k-draft $SPEC_DRAFT_CACHE_TYPE_K"
[ -z "$SPEC_DRAFT_CACHE_TYPE_V" ] || MODEL_SPECIFIC_ARGS="$MODEL_SPECIFIC_ARGS
--cache-type-v-draft $SPEC_DRAFT_CACHE_TYPE_V"
# Draft-model MoE CPU offload: SPEC_DRAFT_N_CPU_MOE (exact layer count)
# wins over SPEC_DRAFT_CPU_MOE (every expert layer) when both are set,
# same precedence as the main model's N_CPU_MOE/CPU_MOE.
if [ -n "$SPEC_DRAFT_N_CPU_MOE" ]; then
MODEL_SPECIFIC_ARGS="$MODEL_SPECIFIC_ARGS
--n-cpu-moe-draft $SPEC_DRAFT_N_CPU_MOE"
elif [ "$SPEC_DRAFT_CPU_MOE" = "1" ]; then
MODEL_SPECIFIC_ARGS="$MODEL_SPECIFIC_ARGS
--cpu-moe-draft"
fi
fi
if [ "${MOE_CACHE_MIB:-0}" != "0" ] && [ -n "$MOE_CACHE_MIB" ]; then
if ! "$BIN" --help 2>&1 | grep -- '--moe-cache-mib' >/dev/null; then
echo "Error: model $NAME requests MOE_CACHE_MIB but this llama-server does not support it. Run localai update or set MOE_CACHE_MIB=0." >&2
exit 1
fi
MODEL_SPECIFIC_ARGS="$MODEL_SPECIFIC_ARGS
--moe-cache-mib $MOE_CACHE_MIB"
fi
# MoE CPU offload: N_CPU_MOE (exact layer count) wins over CPU_MOE (every
# expert layer) when both are set, same precedence as llama-server itself.
if [ -n "$N_CPU_MOE" ]; then
MODEL_SPECIFIC_ARGS="$MODEL_SPECIFIC_ARGS
--n-cpu-moe $N_CPU_MOE"
elif [ "$CPU_MOE" = "1" ]; then
MODEL_SPECIFIC_ARGS="$MODEL_SPECIFIC_ARGS
--cpu-moe"
fi
[ -z "$OVERRIDE_TENSOR" ] || MODEL_SPECIFIC_ARGS="$MODEL_SPECIFIC_ARGS
--override-tensor $(shell_quote_token "$OVERRIDE_TENSOR")"
[ -z "$REASONING" ] || MODEL_SPECIFIC_ARGS="$MODEL_SPECIFIC_ARGS
--reasoning $REASONING"
[ -z "$REASONING_BUDGET" ] || MODEL_SPECIFIC_ARGS="$MODEL_SPECIFIC_ARGS
--reasoning-budget $REASONING_BUDGET"
[ -z "$REASONING_FORMAT" ] || MODEL_SPECIFIC_ARGS="$MODEL_SPECIFIC_ARGS
--reasoning-format $REASONING_FORMAT"
[ "$REASONING_PRESERVE" = "1" ] && MODEL_SPECIFIC_ARGS="$MODEL_SPECIFIC_ARGS
--reasoning-preserve"
# --mmproj-url is only emitted when no local MMPROJ (explicit or
# folder-auto-detected) is already in play, since the two are alternatives.
if [ -n "$MMPROJ_URL" ] && [ -z "$MMPROJ" ]; then
MODEL_SPECIFIC_ARGS="$MODEL_SPECIFIC_ARGS
--mmproj-url $(shell_quote_token "$MMPROJ_URL")"
fi
if [ "$MMPROJ_OFFLOAD" = "1" ] && { [ -n "$MMPROJ" ] || [ -n "$MMPROJ_URL" ]; }; then
MODEL_SPECIFIC_ARGS="$MODEL_SPECIFIC_ARGS
--mmproj-offload"
fi
[ -z "$IMAGE_MIN_TOKENS" ] || MODEL_SPECIFIC_ARGS="$MODEL_SPECIFIC_ARGS
--image-min-tokens $IMAGE_MIN_TOKENS"
[ -z "$IMAGE_MAX_TOKENS" ] || MODEL_SPECIFIC_ARGS="$MODEL_SPECIFIC_ARGS
--image-max-tokens $IMAGE_MAX_TOKENS"
# llama-server takes one --lora/--lora-scaled flag with a comma-separated
# list (repeating the flag is not documented behavior), so multiple
# comma-separated entries are rejoined into a single quoted argv token
# here rather than emitted as separate flags.
if [ -n "$LORA" ]; then
IFS=',' read -r -a LORA_ARRAY <<< "$LORA"
LORA_JOINED=""
for LORA_PATH in "${LORA_ARRAY[@]}"; do
LORA_PATH="$(trim_ws "$LORA_PATH")"
[ -n "$LORA_PATH" ] || continue
LORA_JOINED="${LORA_JOINED:+$LORA_JOINED,}$LORA_PATH"
done
[ -z "$LORA_JOINED" ] || MODEL_SPECIFIC_ARGS="$MODEL_SPECIFIC_ARGS
--lora $(shell_quote_token "$LORA_JOINED")"
fi
if [ -n "$LORA_SCALED" ]; then
IFS=',' read -r -a LORA_SCALED_ARRAY <<< "$LORA_SCALED"
LORA_SCALED_JOINED=""
for LORA_SCALED_ENTRY in "${LORA_SCALED_ARRAY[@]}"; do
LORA_SCALED_ENTRY="$(trim_ws "$LORA_SCALED_ENTRY")"
[ -n "$LORA_SCALED_ENTRY" ] || continue
LORA_SCALED_JOINED="${LORA_SCALED_JOINED:+$LORA_SCALED_JOINED,}$LORA_SCALED_ENTRY"
done
[ -z "$LORA_SCALED_JOINED" ] || MODEL_SPECIFIC_ARGS="$MODEL_SPECIFIC_ARGS
--lora-scaled $(shell_quote_token "$LORA_SCALED_JOINED")"
fi
[ -z "$EXTRA_ARGS_RENDERED" ] || MODEL_SPECIFIC_ARGS="$MODEL_SPECIFIC_ARGS$EXTRA_ARGS_RENDERED"
EXTRA_MODEL_YAML=""
if [ "$DISABLE_KEEP_ALIVES" = "1" ]; then
require_swap_version "DISABLE_KEEP_ALIVES (model $NAME)" 263
EXTRA_MODEL_YAML="$EXTRA_MODEL_YAML
disableKeepAlives: true"
fi
[ -z "$TTL" ] || EXTRA_MODEL_YAML="$EXTRA_MODEL_YAML
ttl: $TTL"
if [ -n "$ALIASES" ]; then
EXTRA_MODEL_YAML="$EXTRA_MODEL_YAML
aliases:"
IFS=',' read -r -a ALIAS_ARRAY <<< "$ALIASES"
for ALIAS in "${ALIAS_ARRAY[@]}"; do
ALIAS="$(trim_ws "$ALIAS")"
[ -n "$ALIAS" ] || continue
case "$ALIAS" in
*\"*|*"'"*|*'`'*|*\\*)
echo "Warning: skipping unsupported alias for model $NAME: $ALIAS" >&2
continue
;;
esac
EXTRA_MODEL_YAML="$EXTRA_MODEL_YAML
- \"$ALIAS\""
done
fi
if [ -n "$SET_TEMPERATURE" ] || [ -n "$SET_TOP_P" ]; then
EXTRA_MODEL_YAML="$EXTRA_MODEL_YAML
filters:
setParams:"
[ -z "$SET_TEMPERATURE" ] || EXTRA_MODEL_YAML="$EXTRA_MODEL_YAML
temperature: $SET_TEMPERATURE"
[ -z "$SET_TOP_P" ] || EXTRA_MODEL_YAML="$EXTRA_MODEL_YAML
top_p: $SET_TOP_P"
fi
MODEL_COUNT=$((MODEL_COUNT + 1))
cat >> "$CONFIG" <<MODELCFG
"$NAME":
# localai-model-type: $MODEL_TYPE
proxy: http://$LOCALAI_LISTEN_HOST:\${PORT}
cmd: >
"$BIN"
--port \${PORT}
--model "$MODEL"
$MODEL_EXTRA_ARGS
--n-gpu-layers $N_GPU_LAYERS
-t $THREADS
--ctx-size $CTX_SIZE
--cache-type-k $CACHE_TYPE_K
--cache-type-v $CACHE_TYPE_V
$MODEL_SPECIFIC_ARGS
$COMMON_EXTRA_ARGS
MODELCFG
if [ -n "$EXTRA_MODEL_YAML" ]; then
printf '%s\n' "$EXTRA_MODEL_YAML" >> "$CONFIG"
fi
printf '\n' >> "$CONFIG"
done <<< "$MODEL_ENTRIES"
# Commit the two output files as one unit. Each rename is atomic, but a
# failure between them must not leave keys.yaml and config.yaml describing
# different key sets; keep backup copies and roll config.yaml back if the
# keys file cannot be committed.
CONFIG_BACKUP="$(mktemp "${CONFIG_DEST}.previous.XXXXXX")" || exit 1
HAVE_CONFIG_PREV=0
if [ -f "$CONFIG_DEST" ]; then
if ! cp -p -- "$CONFIG_DEST" "$CONFIG_BACKUP"; then
echo "Error: could not back up $CONFIG_DEST; it was left unchanged." >&2
exit 1
fi
HAVE_CONFIG_PREV=1
fi
KEYS_BACKUP="$(mktemp "${KEYS_DEST}.previous.XXXXXX")" || exit 1
HAVE_KEYS_PREV=0
if [ -f "$KEYS_DEST" ]; then
if ! cp -p -- "$KEYS_DEST" "$KEYS_BACKUP"; then
echo "Error: could not back up $KEYS_DEST; $CONFIG_DEST was left unchanged." >&2
exit 1
fi
HAVE_KEYS_PREV=1
fi
if ! mv -f -- "$CONFIG" "$CONFIG_DEST"; then
echo "Error: failed to install new configuration; $CONFIG_DEST was left unchanged." >&2
exit 1
fi
if [ "$ACTIVE_API_KEY_COUNT" -gt 0 ]; then
if ! mv -f -- "$KEYS_FILE" "$KEYS_DEST"; then
if [ "$HAVE_CONFIG_PREV" -eq 1 ]; then
mv -f -- "$CONFIG_BACKUP" "$CONFIG_DEST" || true
else
rm -f -- "$CONFIG_DEST"
fi
if [ "$HAVE_KEYS_PREV" -eq 1 ] && [ ! -f "$KEYS_DEST" ]; then
mv -f -- "$KEYS_BACKUP" "$KEYS_DEST" || true
fi
echo "Error: failed to install $KEYS_DEST; restored previous $CONFIG_DEST." >&2
exit 1
fi
else
if ! rm -f -- "$KEYS_DEST"; then
if [ "$HAVE_CONFIG_PREV" -eq 1 ]; then
mv -f -- "$CONFIG_BACKUP" "$CONFIG_DEST" || true
else
rm -f -- "$CONFIG_DEST"
fi
echo "Error: failed to remove $KEYS_DEST; restored previous $CONFIG_DEST." >&2
exit 1
fi
fi
rm -f -- "$CONFIG_BACKUP" "$KEYS_BACKUP"
CONFIG="$CONFIG_DEST"
# Do not delete the committed destinations when the EXIT trap runs.
trap - EXIT
if [ "$ACTIVE_API_KEY_COUNT" -gt 0 ]; then
echo "Generated $CONFIG with $MODEL_COUNT model(s) and $ACTIVE_API_KEY_COUNT active API key(s)."
else
echo "Generated $CONFIG with $MODEL_COUNT model(s)."
fi