Skip to content

server: save and clear idle slots on new task (--clear-idle) - #20993

Merged
ngxson merged 11 commits into
ggml-org:masterfrom
yychyo:server-kv-keep-only-active
Apr 3, 2026
Merged

ngxson merged 11 commits into
ggml-org:masterfrom
yychyo:server-kv-keep-only-active

Conversation

@yychyo

@yychyo yychyo commented Mar 25, 2026

Copy link
Copy Markdown
Contributor

In unified KV cache mode, idle slots' KV cells stay in the [0, n_kv) range
and inflate attention cost for all active sequences (even though they're masked).

--clear-idle saves idle slots to --cache-ram and clears them from VRAM, reducing n_kv to only active tokens.

Requires --cache-ram and unified KV cache. Disable with --no-clear-idle.

Benchmarks (agentic workload with multiple agents)

Open questions

Related

Requirements

  • I have read and agree with the contributing guidelines
  • AI usage disclosure: YES ✨, assisted with research and test scaffolding. All code reviewed and understood.

@ggml-gh-bot

ggml-gh-bot Bot commented Mar 25, 2026

Copy link
Copy Markdown

Hi @yychyo, thanks for your contribution!

Per our contribution guidelines, the automated PR checker found the following issue(s) that need your attention:

  • AI-generated content: This project does not accept PRs, descriptions or commit messages that are fully or predominantly AI-generated. If you have used AI to assist you in writing code, please make sure to disclose that explicitly.

Please note that maintainers reserve the right to make final decisions on PRs. If you believe there is a mistake, please comment below.

@yychyo

yychyo commented Mar 26, 2026

Copy link
Copy Markdown
Contributor Author

@yychyo
yychyo force-pushed the server-kv-keep-only-active branch from a9cde4e to 231926b Compare March 26, 2026 12:42
@yychyo
yychyo requested a review from a team as a code owner March 26, 2026 12:42
Comment on lines -1013 to +1037
// don't update the cache if the slot's context is empty
update_cache = update_cache && tokens.size() > 0;

if (update_cache) {
SRV_WRN("%s", "updating prompt cache\n");

const int64_t t_start = ggml_time_us();

ret->prompt_save(*prompt_cache);
// don't save the slot's state if its context is empty
if (tokens.size() > 0) {
ret->prompt_save(*prompt_cache);
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's makes it possible to read an entry from cache to empty slot.

Comment thread tools/server/server-context.cpp Outdated
@@ -2689,6 +2705,34 @@ struct server_context_impl {
n_empty_consecutive = 0;
}

if (kv_keep_only_active && batch.n_tokens > 0) { // LLAMA_KV_KEEP_ONLY_ACTIVE: clear idle slots' KV

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Main logic - under feature flag.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't it better to clear the idle slot as soon as it becomes idle? I.e. when we call slot.release()?

@yychyo yychyo Mar 27, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! That is my first contribution, so my vision quite limited - thank you so much for guidance! Done in 2563b4a

@yychyo yychyo Mar 28, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added f6c4a3d . Also clear idle slots on launch, not just release. Release alone doesn't cover sequential requests (last slot has no reason to clear if nobody else needs VRAM).

@@ -2010,7 +2010,7 @@ server_prompt * server_prompt_cache::alloc(const server_prompt & prompt, size_t
bool server_prompt_cache::load(server_prompt & prompt, const server_tokens & tokens_new, llama_context * ctx, int32_t id_slot) {
const int lcp_best = prompt.tokens.get_common_prefix(tokens_new);

float f_keep_best = float(lcp_best) / prompt.tokens.size();
float f_keep_best = prompt.tokens.size() > 0 ? float(lcp_best) / prompt.tokens.size() : -1.0f; // empty slot: any cache entry wins

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Enables the cache for empty slot.

@github-actions github-actions Bot added examples python python script changes server labels Mar 26, 2026
@ggerganov

Copy link
Copy Markdown
Member

Thanks, this should be a good improvement for unified KV cache setups. I have yet to take a detailed look at how you implemented it. In the meantime, hopefully we get some feedback from people with CUDA backend and -np 4 -kvu - the performance over multiple long requests should degrade much less if this works correctly.

Comment thread tools/server/server-context.cpp Outdated
Comment on lines +868 to +882
// LLAMA_KV_KEEP_ONLY_ACTIVE: clear idle slots' KV from VRAM before each decode batch
{
const char * env = getenv("LLAMA_KV_KEEP_ONLY_ACTIVE");
if (env && atoi(env)) {
if (!params_base.kv_unified) {
SRV_WRN("%s\n", "LLAMA_KV_KEEP_ONLY_ACTIVE requires unified KV cache, ignoring");
} else if (params_base.cache_ram_mib == 0) {
SRV_WRN("%s\n", "LLAMA_KV_KEEP_ONLY_ACTIVE requires --cache-ram, ignoring");
} else {
kv_keep_only_active = true;
SRV_INF("%s\n", "LLAMA_KV_KEEP_ONLY_ACTIVE: idle slots' KV will be cleared from VRAM before each decode");
}
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we might want kv_keep_only_active to always be on by default when kv_unified == true and can be disabled with a CLI argument.

@yychyo yychyo Mar 27, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in ced2476

@yychyo
yychyo requested a review from a team as a code owner March 27, 2026 20:08
@yychyo yychyo changed the title server : add LLAMA_KV_KEEP_ONLY_ACTIVE to clear idle slots' KV server: clear idle slots KV on release (--kv-clear-idle) Mar 27, 2026
@yychyo

yychyo commented Mar 27, 2026

Copy link
Copy Markdown
Contributor Author

Thanks, this should be a good improvement for unified KV cache setups. I have yet to take a detailed look at how you implemented it. In the meantime, hopefully we get some feedback from people with CUDA backend and -np 4 -kvu - the performance over multiple long requests should degrade much less if this works correctly.

I've made a bench script for "agentic" - like workflow:

Code
#!/usr/bin/env bash

HOST=127.0.0.1
PORT=8990
URL="http://$HOST:$PORT"
N_PREDICT="${N_PREDICT:-512}"
DELAY="${DELAY:-0}"

SERVER_CMD="${SERVER_CMD:-./build-cuda/bin/llama-server \
    -hf bartowski/Qwen_Qwen3.5-4B-GGUF:IQ4_NL \
    --host $HOST --port $PORT -ngl 99 -c 131072 --cache-ram 40960 \
    --swa-checkpoints 32 --cache-type-k q8_0 --jinja \
    --slots --temp 0.0 -b 256 -ub 256}"

make_prompt() {
    local user=$1 turn=$2
    local filler="The ancient library stood at the edge of the forgotten city where scholars gathered for centuries to study mysteries of the universe and decode symbols carved into walls of the great hall. "
    local grow="Additional context was provided by the researchers who continued their investigation into the deeper layers of understanding that emerged from careful analysis of primary sources and artifacts. "
    local p="Agent ${user}: "
    p+=$(printf "${filler}%.0s" $(seq 1 500))
    for ((r=0; r<(turn+1)*140; r++)); do p+="$grow"; done
    echo "$p"
}

post_agent() {
    local turn=$1 id=$2 outdir="$3"
    local t0=$SECONDS delay=0
    if [[ "$turn" -gt 0 && "$DELAY" -gt 0 ]]; then delay=$((RANDOM % DELAY)); sleep "$delay"; fi
    printf "  → agent %d turn %d%s\n" "$id" "$turn" "$( (( delay )) && echo " (delay=${delay}s)" )" >&2
    local prompt=$(make_prompt "$id" "$turn")
    local resp=$(printf '%s' "$prompt" | jq -Rs --argjson np "$N_PREDICT" '{prompt:., n_predict:$np, cache_prompt:true}' |
        curl -sf "$URL/completion" -H "Content-Type: application/json" -d @-) || { echo "curl failed for agent $id turn $turn" >&2; return 1; }
    echo "$resp" | jq -r --arg t "$turn" --arg aid "$id" '"\($t) \($aid) \(.timings.predicted_per_second) \(.timings.prompt_n)"' > "$outdir/turn${turn}_agent${id}.txt"
    local tg=$(echo "$resp" | jq .timings.predicted_per_second)
    local pn=$(echo "$resp" | jq .timings.prompt_n)
    printf "  ← agent %d turn %d: TG=%.1f prompt_n=%d (%ds)\n" "$id" "$turn" "$tg" "$pn" "$((SECONDS-t0))" >&2
}

if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
  set -euo pipefail
  export SCRIPT_DIR="$(realpath "$(dirname "${BASH_SOURCE[0]}")")"
  export N_AGENTS="${1:-4}"
  export N_WORKERS="${2:-2}"
  export N_PREDICT DELAY
  LOGDIR="logs/bench-$(date +%Y%m%d-%H%M%S)-agentic"
  mkdir -p "$LOGDIR"
  export LOGDIR

  SERVER_PID=""
  stop_server() {
      if [[ -n "$SERVER_PID" ]]; then
          kill "$SERVER_PID" 2>/dev/null || true
          wait "$SERVER_PID" 2>/dev/null || true
          SERVER_PID=""
      fi
  }
  trap stop_server EXIT

  start_server() {
      local logfile="$1"; shift
      # shellcheck disable=SC2086
      $SERVER_CMD "$@" >"$logfile" 2>&1 &
      SERVER_PID=$!
      for _ in $(seq 1 60); do curl -sf "$URL/health" >/dev/null 2>&1 && break; sleep 1; done
      curl -sf "$URL/health" >/dev/null 2>&1 || { echo "server failed to start" >&2; exit 1; }
  }

  wait_port_free() { while curl -sf "$URL/health" >/dev/null 2>&1; do sleep 1; done; }

  run_phase() {
      local label="$1"; shift
      local outdir="$LOGDIR/$label"
      mkdir -p "$outdir"
      export OUTDIR="$outdir"

      echo "--- $label ---" >&2
      start_server "$LOGDIR/$label.log" "$@"
      make -j"$N_WORKERS" AGENTS="$(seq -s' ' 0 $((N_AGENTS-1)))" -f <(cat <<'__EOF'
SHELL := bash
.SHELLFLAGS := -eu -o pipefail -c
export BASH_ENV := $(SCRIPT_DIR)/bench-kv-agentic.sh

LAST_TURN := $(foreach a,$(AGENTS),$(OUTDIR)/turn3_agent$(a).txt)

.DELETE_ON_ERROR:
.SUFFIXES:
.PHONY: all
all: $(LAST_TURN)

# chain turns per agent: turn1 depends on turn0, turn2 on turn1, turn3 on turn2
$(foreach a,$(AGENTS),\
  $(foreach t,1 2 3,\
    $(eval $(OUTDIR)/turn$(t)_agent$(a).txt: $(OUTDIR)/turn$(shell echo $$(($(t)-1)))_agent$(a).txt)))

# pattern rule per agent: extract turn from filename stem (turn1_agent0 -> turn=1)
$(foreach a,$(AGENTS),\
  $(eval $(OUTDIR)/turn%_agent$(a).txt: ; @post_agent $$(firstword $$(subst _, ,$$*)) $(a) $(OUTDIR)))
__EOF
    )
      stop_server
      wait_port_free
  }

  echo "=== Agentic Parallel ($N_AGENTS agents, $N_WORKERS workers, n_predict=$N_PREDICT) ==="
  echo "SERVER_CMD: $SERVER_CMD"

  run_phase baseline --no-kv-clear-idle
  run_phase feature

  echo ""
  echo "| Turn | Agent | Baseline TG | Feature TG | Prompt tokens |"
  echo "|------|-------|-------------|------------|---------------|"
  for turn in 0 1 2 3; do
      for agent in $(seq 0 $((N_AGENTS-1))); do
          bl=($(cat "$LOGDIR/baseline/turn${turn}_agent${agent}.txt"))
          ft=($(cat "$LOGDIR/feature/turn${turn}_agent${agent}.txt"))
          printf "| %s | %s | %.1f t/s | %.1f t/s | %s |\n" "$turn" "$agent" "${bl[2]}" "${ft[2]}" "${bl[3]}"
      done
  done
  echo ""
  echo "Logs: $LOGDIR/"
fi

Runs N agents, M active at a time (make -j), with optional random delay between turns. Each agent has a unique prefix and growing context (~20k→32k tokens over 4 turns).

RTX 3090, Qwen3.5-4B IQ4_NL, 8 agents / 4 slots / 2 concurrent, --cache-ram 40960, 131K ctx:

Benchmark
❯ DELAY=5 ./bench-kv-agentic.sh 8 2 2>&1 | tee agentic.log
=== Agentic Parallel (8 agents, 2 workers, n_predict=512) ===
SERVER_CMD: ./build-cuda/bin/llama-server     -hf bartowski/Qwen_Qwen3.5-4B-GGUF:IQ4_NL     --host 127.0.0.1 --port 8990 -ngl 99 -c 131072 --cache-ram 40960     --swa-checkpoints 32 --cache-type-k q8_0 --jinja     --slots --temp 0.
0 -b 256 -ub 256
...

Log

Turn Agent Baseline TG Feature TG Prompt tokens
0 0 44.8 t/s 44.3 t/s 20425
0 1 42.1 t/s 41.1 t/s 20425
0 2 67.3 t/s 84.4 t/s 20425
0 3 64.7 t/s 88.4 t/s 20425
0 4 29.4 t/s 43.9 t/s 20425
0 5 30.1 t/s 40.8 t/s 20425
0 6 29.7 t/s 43.8 t/s 20425
0 7 71.8 t/s 88.1 t/s 20425
1 0 68.0 t/s 70.2 t/s 3924
1 1 54.2 t/s 83.7 t/s 3924
1 2 51.8 t/s 81.5 t/s 3924
1 3 65.3 t/s 70.0 t/s 3924
1 4 64.8 t/s 78.0 t/s 3924
1 5 50.9 t/s 62.3 t/s 3924
1 6 51.7 t/s 55.8 t/s 3924
1 7 65.2 t/s 58.2 t/s 3924
2 0 66.0 t/s 55.3 t/s 3924
2 1 42.6 t/s 44.3 t/s 3924
2 2 60.5 t/s 35.6 t/s 3924
2 3 47.8 t/s 50.7 t/s 3924
2 4 42.7 t/s 81.1 t/s 3924
2 5 47.8 t/s 54.0 t/s 3924
2 6 67.5 t/s 36.1 t/s 3924
2 7 47.2 t/s 51.1 t/s 3924
3 0 59.9 t/s 48.2 t/s 3924
3 1 28.0 t/s 80.1 t/s 3924
3 2 32.2 t/s 69.4 t/s 3924
3 3 39.2 t/s 58.3 t/s 3924
3 4 44.6 t/s 47.6 t/s 3924
3 5 23.9 t/s 80.3 t/s 3924
3 6 31.5 t/s 68.4 t/s 3924
3 7 61.9 t/s 79.6 t/s 3924
Metric Baseline Feature Delta
TG avg 49.8 t/s 61.7 t/s +24%
Wall time 321s 213s -34%
Cache updates avg 1892ms 768ms -59%

@yychyo

yychyo commented Mar 28, 2026

Copy link
Copy Markdown
Contributor Author

💡 Idea: cooldown before eviction

In parallel agentic workloads with fast tool calls (seconds apart), slots get evicted and immediately restored. --no-kv-clear-idle N (default 0 = immediate) could add a cooldown before clearing on release.

👍 / 👎 ?

@yychyo
yychyo force-pushed the server-kv-keep-only-active branch from b9a4e1f to d658a62 Compare March 28, 2026 20:16
@strawberrymelonpanda

Copy link
Copy Markdown
Contributor

feedback from people with CUDA backend and -np 4 -kvu
RTX 3090, Qwen3.5-4B IQ4_NL, 8 agents / 4 slots / 2 concurrent, --cache-ram 40960, 131K ctx:

Was just playing around with --parallel and -kvu today. -np 2 and -kvu together seem pretty good on my system with a 3090 and Qwen 27B, but @yychyo's 8 agents, 2 workers, 88+ t/s makes me feel like I'm doing something very wrong. 😆

Haven't had a chance to bench this branch yet, but watching the PR with interest.

@yychyo

yychyo commented Mar 31, 2026

Copy link
Copy Markdown
Contributor Author

feedback from people with CUDA backend and -np 4 -kvu
RTX 3090, Qwen3.5-4B IQ4_NL, 8 agents / 4 slots / 2 concurrent, --cache-ram 40960, 131K ctx:

Was just playing around with --parallel and -kvu today. -np 2 and -kvu together seem pretty good on my system with a 3090 and Qwen 27B, but @yychyo's 8 agents, 2 workers, 88+ t/s makes me feel like I'm doing something very wrong. 😆

Haven't had a chance to bench this branch yet, but watching the PR with interest.

Thanks for the feedback. My use-case (not only mine, I think it is quite popular nowadays) is a variety of agents, each of them are either a) waiting either on user input or a tool call b) calling llama-server. For the llama-server point of view those are calls with "growing" prompts (with added user input/tool output) that are done sporadically.

The issue I faced is that current (CUDA mostly I think) implementation operates on the whole unified cache (even with non-active sequences in it). So, if you used several agents, and have some slots filled in, and running just 1 sequence, the inference speed will be much worse (25-30%) compared if you runned the same sequence alone.

This PR "offloads" the idle sequences to VRAM. I use llama-server with it locally, and really satisfied how it works with opencode or pi. The "proper" fix would be to make CUDA kernels to be able work efficiently in this use case - do not process what is not needed to be processed (I explored this path a bit, and it seems to be very complex) .

Overall, during work on this PR I got some understanding on further future improvement of the whole caching logic (the most impactful thing for me seems ability to support prefix sharing in ram cache + save/restore path, but that needs discussion, design and so on, and I'm limited to 1 PR as a first-time contributor, so I'll try to get it to completion first)

@strawberrymelonpanda For the -np 2 : this PR should improve the use cases , when you have an active sequence in one of two slots in you system, and not active in the other. Without the PR, llama-server will "waste" compute of operating on the whole unified cache (active sequence, and not active, and then discarding uneeded data with KQ mask). With this PR, unactive sequence will be offloaded to RAM, infrerence should be notable faster. It also depends on the length of you r sequences. If you have sequences in say 8k range, you will barely noticed the difference. If you have them like 32k+ range, it will be noticeable.

@strawberrymelonpanda

strawberrymelonpanda commented Mar 31, 2026

Copy link
Copy Markdown
Contributor

Oh yeah, I think I get the idea and I like it. Right now my main consideration is just:

paraelle 1 = 100K unified fp16 cache
paraelle 2 = 95K
paraelle 4 = 86K
(etc)

-NP 1 runs noticeably slower than -NP 2 in some scenarios, so the 5K ctx loss is worth it. NP 4 however doesn't run noticeably faster in my tests than NP 2, and sometimes actually slower, so losing 9K cache isn't great.

But this branch could make higher parallel values more worthwhile.

@yychyo

yychyo commented Mar 31, 2026

Copy link
Copy Markdown
Contributor Author

Did a test for qwen3.5-27b.

Code of the bench-kv-agentic.sh
#!/usr/bin/env bash
# Sourced by Make recipes via BASH_ENV for post_agent/make_prompt.
# Executed directly as the main entry point.

HOST=127.0.0.1
PORT=8990
URL="http://$HOST:$PORT"
N_PREDICT="${N_PREDICT:-512}"
DELAY="${DELAY:-0}"

SERVER_CMD="${SERVER_CMD:-./build-cuda/bin/llama-server \
    -hf bartowski/Qwen_Qwen3.5-27B-GGUF:IQ4_NL \
    --host $HOST --port $PORT -ngl 99 -c 131072 --cache-ram 40960 \
    --swa-checkpoints 32 --cache-type-k q8_0 --jinja \
    --slots --temp 0.0 -b 256 -ub 256}"

agents() { seq 0 $((N_AGENTS - 1)); }

make_prompt() {
    local user=$1 turn=$2
    local filler="The ancient library stood at the edge of the forgotten city where scholars gathered for centuries to study mysteries of the universe and decode symbols carved into walls of the great hall. "
    local grow="Additional context was provided by the researchers who continued their investigation into the deeper layers of understanding that emerged from careful analysis of primary sources and artifacts. "
    local p="Agent ${user}: "
    p+=$(printf "${filler}%.0s" $(seq 1 500))
    for ((r=0; r<(turn+1)*140; r++)); do p+="$grow"; done
    echo "$p"
}

post_agent() {
    local turn=$1 id=$2 outdir="$3"
    local t0=$SECONDS delay=0
    if [[ "$turn" -gt 0 && "$DELAY" -gt 0 ]]; then delay=$((RANDOM % DELAY)); sleep "$delay"; fi
    printf "  → agent %d turn %d%s\n" "$id" "$turn" "$( (( delay )) && echo " (delay=${delay}s)" )" >&2
    local prompt=$(make_prompt "$id" "$turn")
    local resp=$(printf '%s' "$prompt" | jq -Rs --argjson np "$N_PREDICT" '{prompt:., n_predict:$np, cache_prompt:true}' |
        curl -sf "$URL/completion" -H "Content-Type: application/json" -d @-) || { echo "curl failed for agent $id turn $turn" >&2; return 1; }
    echo "$resp" | jq -r --arg t "$turn" --arg aid "$id" '"\($t) \($aid) \(.timings.predicted_per_second) \(.timings.prompt_n)"' > "$outdir/turn${turn}_agent${id}.txt"
    local tg=$(echo "$resp" | jq .timings.predicted_per_second)
    local pn=$(echo "$resp" | jq .timings.prompt_n)
    printf "  ← agent %d turn %d: TG=%.1f prompt_n=%d (%ds)\n" "$id" "$turn" "$tg" "$pn" "$((SECONDS-t0))" >&2
}

main() {
    set -euo pipefail
    export SCRIPT_DIR="$(realpath "$(dirname "${BASH_SOURCE[0]}")")"
    export N_AGENTS="${1:-4}"
    export N_WORKERS="${2:-2}"
    export N_PREDICT DELAY
    LOGDIR="logs/bench-$(date +%Y%m%d-%H%M%S)-agentic"
    mkdir -p "$LOGDIR"
    export LOGDIR

    SERVER_PID=""
    stop_server() {
        if [[ -n "$SERVER_PID" ]]; then
            kill "$SERVER_PID" 2>/dev/null || true
            wait "$SERVER_PID" 2>/dev/null || true
            SERVER_PID=""
        fi
    }
    trap stop_server EXIT

    start_server() {
        local logfile="$1"; shift
        # shellcheck disable=SC2086
        $SERVER_CMD "$@" >"$logfile" 2>&1 &
        SERVER_PID=$!
        for _ in $(seq 1 60); do curl -sf "$URL/health" >/dev/null 2>&1 && break; sleep 1; done
        curl -sf "$URL/health" >/dev/null 2>&1 || { echo "server failed to start" >&2; exit 1; }
    }

    wait_port_free() { while curl -sf "$URL/health" >/dev/null 2>&1; do sleep 1; done; }

    run_phase() {
        local label="$1"; shift
        local outdir="$LOGDIR/$label"
        mkdir -p "$outdir"
        export OUTDIR="$outdir"

        echo "--- $label ---" >&2
        start_server "$LOGDIR/$label.log" "$@"
        make -j"$N_WORKERS" AGENTS="$(agents | tr '\n' ' ')" -f <(cat <<'__EOF'
SHELL := bash
.SHELLFLAGS := -eu -o pipefail -c
export BASH_ENV := $(SCRIPT_DIR)/bench-kv-agentic.sh

LAST_TURN := $(foreach a,$(AGENTS),$(OUTDIR)/turn3_agent$(a).txt)

.DELETE_ON_ERROR:
.SUFFIXES:
.PHONY: all
all: $(LAST_TURN)

# chain turns per agent: turn1 depends on turn0, turn2 on turn1, turn3 on turn2
$(foreach a,$(AGENTS),\
  $(foreach t,1 2 3,\
    $(eval $(OUTDIR)/turn$(t)_agent$(a).txt: $(OUTDIR)/turn$(shell echo $$(($(t)-1)))_agent$(a).txt)))

# pattern rule per agent: extract turn from filename stem (turn1_agent0 -> turn=1)
$(foreach a,$(AGENTS),\
  $(eval $(OUTDIR)/turn%_agent$(a).txt: ; @post_agent $$(firstword $$(subst _, ,$$*)) $(a) $(OUTDIR)))
__EOF
        )
        stop_server
        wait_port_free
    }

    echo "=== Agentic Parallel ($N_AGENTS agents, $N_WORKERS workers, n_predict=$N_PREDICT) ==="
    echo "SERVER_CMD: $SERVER_CMD"

    run_phase baseline --no-kv-clear-idle
    run_phase feature

    echo ""
    echo "| Turn | Agent | Baseline TG | Feature TG | Prompt tokens |"
    echo "|------|-------|-------------|------------|---------------|"
    for turn in 0 1 2 3; do
        for agent in $(agents); do
            bl=($(cat "$LOGDIR/baseline/turn${turn}_agent${agent}.txt"))
            ft=($(cat "$LOGDIR/feature/turn${turn}_agent${agent}.txt"))
            printf "| %s | %s | %.1f t/s | %.1f t/s | %s |\n" "$turn" "$agent" "${bl[2]}" "${ft[2]}" "${bl[3]}"
        done
    done
    echo ""
    echo "Logs: $LOGDIR/"
}

if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
    main "$@"
fi

So I called it like this : DELAY=5 ./bench-kv-agentic.sh 8 2 2>&1 - it means 8 agents, only 2 active at a time, put 5 sec as a delay to emulate tool call/user input

Results
Turn Agent Baseline TG Feature TG Prompt tokens
0 0 12.0 t/s 11.3 t/s 20425
0 1 11.0 t/s 12.0 t/s 20425
0 2 22.9 t/s 25.1 t/s 20425
0 3 22.3 t/s 26.1 t/s 20425
0 4 8.9 t/s 11.9 t/s 20425
0 5 8.9 t/s 11.1 t/s 20425
0 6 9.1 t/s 11.9 t/s 20425
0 7 16.2 t/s 19.3 t/s 20425
1 0 16.1 t/s 19.1 t/s 3924
1 1 15.0 t/s 16.5 t/s 3924
1 2 14.5 t/s 16.5 t/s 3924
1 3 15.5 t/s 18.8 t/s 3924
1 4 15.3 t/s 18.6 t/s 3924
1 5 12.4 t/s 16.8 t/s 3924
1 6 12.5 t/s 16.8 t/s 3924
1 7 15.4 t/s 18.5 t/s 3924
2 0 15.0 t/s 18.1 t/s 3924
2 1 13.1 t/s 15.9 t/s 3924
2 2 13.2 t/s 15.8 t/s 3924
2 3 14.5 t/s 17.7 t/s 3924
2 4 14.3 t/s 17.6 t/s 3924
2 5 13.2 t/s 16.6 t/s 3924
2 6 13.2 t/s 16.7 t/s 3924
2 7 14.4 t/s 17.6 t/s 3924
3 0 14.2 t/s 17.2 t/s 3924
3 1 12.7 t/s 13.5 t/s 3924
3 2 12.5 t/s 13.5 t/s 3924
3 3 13.6 t/s 16.8 t/s 3924
3 4 13.3 t/s 16.7 t/s 3924
3 5 11.0 t/s 14.5 t/s 3924
3 6 10.9 t/s 14.6 t/s 3924
3 7 18.2 t/s 22.1 t/s 3924

Overall it's +~20% of TG in this scenario (~13.9 t/s vs ~16.7 t/s avg). It's not a "speed up" per se, it's workaround to avoid slowdown though.

@strawberrymelonpanda

strawberrymelonpanda commented Mar 31, 2026

Copy link
Copy Markdown
Contributor

Ran the branch through my own agent evals, which performance-wise is very naive - just opencode run on CLI against various coding tasks sequentially. Note: The variance between runs is high here because I made this script more for evaluating outcome rather than speed.

I can say I see no regression, and early results good. Looks like I indeed no longer see the slowdown in the NP 4 scenario now that I had mentioned earlier. NP 4 may actually be faster than NP 2 now.

(server restarts between runs)

PR:
NP=2: 7m 11s
NP=4 1st run: 7m 14s <-- on par with np2 on average on PR
NP=4 2nd run: 6m 39s <-- or faster

Master:
NP=2: 7m 35s <-- faster than np4 on average on master
NP=4: 1st run: 7m 56s
NP=4 2nd run: 8m 37s

hopefully we get some feedback from people with CUDA backend and -np 4 -kvu

@ggerganov It's not super scientific, but as a "CUDA -np 4 -kvu" user, it's looking good so far for my use.

@ggerganov

ggerganov commented Mar 31, 2026

Copy link
Copy Markdown
Member

In parallel agentic workloads with fast tool calls (seconds apart), slots get evicted and immediately restored.

Hm that's a good point. Maybe my suggestion earlier to clear the idle slot immediately on release wasn't good. If you remove that logic, and move the "clear idle slots" logic to run after the new task has been assigned to a slot, would it work better?

@ngxson
ngxson merged commit 50e0ad0 into ggml-org:master Apr 3, 2026
46 of 49 checks passed
@Farmadupe

Copy link
Copy Markdown

probably a bit too late now as the boat has probably sailed, but --clear-idle is a very awkward name.

  • it's missing a noun (it clears idle what?)
  • 'clear' strongly suggests suggests deletion, but it's actually a caching operation

would have suggested --cache-idle-slots (which would have also paired up with --cache-ram)

@yychyo

yychyo commented Apr 4, 2026

Copy link
Copy Markdown
Contributor Author

would have suggested --cache-idle-slots (which would have also paired up with --cache-ram)

Fair point on the naming. Since it's enabled by default, most users won't need to type it - --no-clear-idle is the more likely flag in practice.

P.S.: If a rename makes sense, happy to do a follow-up.

icex added a commit to icex/llama.cpp that referenced this pull request Apr 5, 2026
Includes:
- server: Fix undefined timing measurement errors (ggml-org#21201)
- server: save and clear idle slots on new task --clear-idle (ggml-org#20993)
- common: fix tool call type detection for nullable/enum schemas (ggml-org#21327)
- CUDA: fix FA kernel selection logic (ggml-org#21271)
- kv-cache: do not quantize SWA KV cache (ggml-org#21277) + revert (ggml-org#21332)
- common/parser: fix call ID detection + atomicity (ggml-org#21230)
- jinja: coerce input for string-specific filters (ggml-org#21370)
- Various CI, HIP, WebGPU, and documentation fixes
@ggerganov

Copy link
Copy Markdown
Member

Yes good point @Farmadupe - let's rename.

XeonBloomfield added a commit to XeonBloomfield/llama.cpp that referenced this pull request Apr 11, 2026
* model, mtmd: fix gguf conversion for audio/vision mmproj (ggml-org#21309)

* fix gguf conversion for audio/vision mmproj

* fix test

* tests: allow exporting graph ops from HF file without downloading weights (ggml-org#21182)

* tests: allow exporting graph ops from HF file without downloading weights

* use unique_ptr for llama_context in HF metadata case

* fix missing non-required tensors falling back to type f32

* use unique pointers where possible

* use no_alloc instead of fixing f32 fallback

* fix missing space

* ggml-webgpu: add vectorized flash attention (ggml-org#20709)

* naive vectorized version

* add vectorized flash attention

* update vec version

* remove unused path and shader

* remove unused helper functions

* add comments

* remove pad path

* ggml-webgpu: fix flash-attn vec nwg=1 path and tighten vec specialization

* change back to vec4

* enable multi split

* enable vec path when:
- Q->ne[1] < 20
- Q->ne[0] % 32 == 0
- V->ne[0] % 4 == 0
- K->type == f16

* update flast_attn_vec_split.wgsl to reduce redundant workgroup barrier usage and use select

* enable vec path for q4 and q8

* flash-attn vec nwg=1 fast path (skip tmp/reduce staging)

* use packed f16 K loads in flash-attn vec split

* use packed f16 K loads in flash-attn vec split on host side

* tune flash-attn vec f16 VEC_NE by head dim

* cleanup

* cleanup

* keep host side clean

* cleanup host side

* change back to original host wait/submit behavior

* formatting

* reverted param-buffer pool r ecfactor

* add helper functions

* ggml-webgpu: move flash-attn vec pipeline caching back into shader lib

* ggml-webgpu: remove duplicate functions

* ggml-webgpu: reserve flash-attn vec scratch in dst buffer allocation

* ggml-webgpu: revert unrelated change

* ggml-webgpu: revert deleted comment

* disable uniformity check

* remove unnecessary change

* Update ggml/src/ggml-webgpu/wgsl-shaders/flash_attn_vec_split.wgsl

* Update ggml/src/ggml-webgpu/ggml-webgpu.cpp

---------

Co-authored-by: Reese Levine <reeselevine1@gmail.com>

* tests : add unit test coverage for llama_tensor_get_type (ggml-org#20112)

* Add unit test coverage for llama_tensor_get_type

* Fix merge conflicts, add more schemas

* clang formatter changes

* Trailing whitespace

* Update name

* Start rebase

* Updating files with upstream changes prior to rebase

* Changes needed from rebase

* Update attn_qkv schema, change throw behaviour

* Fix merge conflicts

* White space

* Update with latest changes to state counters

* Revert accidental personal CLAUDE.md changes

* Change quotation mark

* Reuse metadata.name since we have it

* Move test-only stuff out of llama-quant.cpp

* Hide the regex functionality back in llama-quant.cpp, use a unique pointer to a new struct 'compiled_tensor_type_patterns' which contains the patterns

* cont : inital deslop guidelines

* Cleanup based on review comments

* Continue cleanup

* Small cleanup

* Manually set proper ordering of tensors, mostly applies to gemma

* Formatting

* Update tests/test-quant-type-selection.cpp

Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@scala.com>

* Fix merge conflicts

---------

Co-authored-by: Georgi Gerganov <ggerganov@gmail.com>
Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@scala.com>

* fix: gemma 4 template (ggml-org#21326)

* [HIP] Bump ROCm version to 7.2.1 (ggml-org#21066)

Bump ROCm version on Linux from 7.2 to 7.2.1
Add gfx1102 target
Delete LLVM workaround since ROCm 7.2.1 has fix for ROCm 7.2 perf regression ROCm/rocm-systems#2865

---------

Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@scala.com>

* ci : add AMD ZenDNN label to PR labeler (ggml-org#21345)

* ci : add AMD CPU label to PR labeler
Add automatic labeling for PRs that modify AMD CPU (ZenDNN) backend files

* ci : rename label AMD CPU to AMD ZenDNN in labeler config

Co-authored-by: Aaron Teo <taronaeo@gmail.com>

---------

Co-authored-by: Aaron Teo <taronaeo@gmail.com>

* (revert) kv-cache : do not quantize SWA KV cache (ggml-org#21332)

This reverts commit 17193cc.

* chat : avoid including json in chat.h (ggml-org#21306)

* rpc : reuse compute graph buffers (ggml-org#21299)

Reuse the buffer for the ggml context which is used for creating the
compute graph on the server side. This partially addresses a memory leak
created by the CUDA backend due to using buffer addresses as cache
keys.

ref: ggml-org#21265
ref: ggml-org#20315

* vocab: fix Gemma4 tokenizer (ggml-org#21343)

* seems to work

* fix case with new line

Co-authored-by: sayap <sokann@gmail.com>

* gemma 4: fix pre tok regex

---------

Co-authored-by: Xuan Son Nguyen <son@huggingface.co>
Co-authored-by: sayap <sokann@gmail.com>

* ggml-zendnn : add MUL_MAT_ID op support for MoE models (ggml-org#21315)

* ggml-zendnn : add MUL_MAT_ID op support for MoE models
- Add MUL_MAT_ID op acceleration for Mixture-of-Experts models
- MUL_MAT_ID op fallback to CPU backend if total experts > 32
- Point ZenDNN lib to latest bits ZenDNN-2026-WW13

* ggml-zendnn : add braces to sgemm failure condition for consistency

Co-authored-by: Aaron Teo <taronaeo@gmail.com>

---------

Co-authored-by: Aaron Teo <taronaeo@gmail.com>

* fix: add openssl to nix dependencies (ggml-org#21353) (ggml-org#21355)

* HIP: build eatch ci build test for a different architecture (ggml-org#21337)

This helps improve our chances of finding build failures before the release workflow
builds for all architectures.

* fix: remove stale assert (ggml-org#21369)

* ci: add more binary checks (ggml-org#21349)

* jinja: coerce input for string-specific filters (ggml-org#21370)

* docs: Update build.md: HSA_OVERRIDE_GFX_VERSION clarification (ggml-org#21331)

The `HSA_OVERRIDE_GFX_VERSION` variable can be used in ROCm to override an unsupported target architecture with a similar but supported target architecture.

This does not and has never worked on Windows. I think the clarification could avoid driving Windows people towards this solution that does not work.

* docker : bump cuda12 to 12.9.1 (ggml-org#20920)

Co-authored-by: M1DNYT3 <m1dnyt3@MacBookPro.lan>
Co-authored-by: CISC <CISC@users.noreply.github.com>

* common : fix tool call type detection for nullable and enum schemas (ggml-org#21327)

* common : fix tool call type detection for nullable and enum schemas

* common, tests : fix grammar delegation for nullable/enum schemas and add tests

Fix enum type inference to scan all enum values (not just index 0) so
schemas like {"enum": [0, "celsius"]} correctly detect string type.

Fix schema_delegates in peg-parser to handle nullable type arrays
(["string", "null"]) and typeless enum schemas in raw mode, allowing
the tagged parser to use raw text instead of JSON-formatted strings.

Add test cases for Qwen3-Coder (TAG_WITH_TAGGED format):
- nullable string ["string", "null"]
- nullable string with null first ["null", "string"]
- nullable integer ["integer", "null"]
- enum without explicit type key

* common/parser: fix call ID detection (Mistral parser mostly) + atomicity for tag-json parsers (ggml-org#21230)

* Fix call ID detection (Mistral parser mostly) + atomicity for tag-json parsers

* Rename

* Update common/chat-auto-parser-generator.cpp

Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@scala.com>

---------

Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@scala.com>

* server: save and clear idle slots on new task (`--clear-idle`) (ggml-org#20993)

* server: clear idle slots KV from VRAM (LLAMA_KV_KEEP_ONLY_ACTIVE)

* server: move idle slot KV clearing to slot release

The save "cost" is now paid by the finishing request.

* server: add --kv-clear-idle flag, enable by default

* server: skip clearing last idle slot, clear on launch

* server: test --no-kv-clear-idle flag

* server: simplify on-release clearing loop

* server: remove on-release KV clearing, keep launch-only

* cont : clean-up

* tests: update log strings after --clear-idle rename

* tests: use debug tags instead of log message matching

* test: fix Windows CI by dropping temp log file unlink

---------

Co-authored-by: Georgi Gerganov <ggerganov@gmail.com>

* ci: Add Windows Vulkan backend testing on Intel (ggml-org#21292)

* experimenting CI

* Experimenting CI fix for MinGW

* experimenting CI on Windows

* modified script for integration with VisualStudio

* added proxy handling

* adding python version for Windows execution

* fix iterator::end() dereference

* fixed proxy handling

* Fix errors occurring on Windows

* fixed ci script

* Reverted to master

* Stripping test items to simplify Windows test

* adjusting script for windows testing

* Changed shell

* Fixed shell

* Fixed shell

* Fix CI setting

* Fix CI setting

* Fix CI setting

* Experimenting ci fix

* Experimenting ci fix

* Experimenting ci fix

* Experimenting ci fix

* experimenting fix for unit test error

* Changed to use BUILD_LOW_PERF to skip python tests

* Fix CI

* Added option to specify Ninja generator

* Reverted proxy related changes

* ggml-webgpu: move from parameter buffer pool to single buffer with offsets (ggml-org#21278)

* Work towards removing bitcast

* Move rest of existing types over

* Add timeout back to wait and remove synchronous set_tensor/memset_tensor

* move to unpackf16 for wider compatibility

* cleanup

* Remove deadlock condition in free_bufs

* Start work on removing parameter buffer pools

* Simplify and optimize further

* simplify profile futures

* Fix stride

* Try using a single command buffer per batch

* formatting

* llama: add custom newline split for Gemma 4 (ggml-org#21406)

* llama-model: read final_logit_softcapping for Gemma 4 (ggml-org#21390)

* common : respect specified tag, only fallback when tag is empty (ggml-org#21413)

Signed-off-by: Adrien Gallouët <angt@huggingface.co>

* server: Fix undefined timing measurement errors in server context (ggml-org#21201)

Co-authored-by: Dan Hoffman <dhoffman@cyket.net>

* common : add gemma 4 specialized parser (ggml-org#21418)

* common : add gemma4 dedicated parser

* cont : add '<|tool_response>' as eog

* cont : emit JSON from Gemma4 tool call AST

* cont : more fixes

* cont : refactor convert function

* cont : refine rules and mapping

* cont : add more tests

* cont : clean up

* cont : remove autoparser gemma4 implementation

* cont : more cleanup

* cont : rename gemma4.jinja to match the others

* cont : add custom template to support interleaved thinking

* cont : preserve reasoning in model turns

* cont : fix initializer error

* cont : fix unused vars

* cont : fix accidental static

* cont : fix specialized_template signature

* fix extra semicolon

* remove debug line and extra space [no ci]

* ci: fix vulkan workflow referencing non-existent action (ggml-org#21442)

* ci: lower cuda12 floor to 12.8.1 for broader host compatibility (ggml-org#21438)

Co-authored-by: M1DNYT3 <m1dnyt3@MacBookPro.lan>

* server : fix logging of build + system info (ggml-org#21460)

This PR changes the logging that occurs at startup of llama-server.
Currently, it is redundant (including CPU information twice) and it is
missing the build + commit info.

* ci : use default RISE RISC-V Runners (ggml-org#21263)

* model : add HunyuanOCR support (ggml-org#21395)

* HunyuanOCR: add support for text and vision models

- Add HunyuanOCR vision projector (perceiver-based) with Conv2d merge
- Add separate HUNYUAN_OCR chat template (content-before-role format)
- Handle HunyuanOCR's invalid pad_token_id=-1 in converter
- Fix EOS/EOT token IDs from generation_config.json
- Support xdrope RoPE scaling type
- Add tensor mappings for perceiver projector (mm.before_rms, mm.after_rms, etc.)
- Register HunYuanVLForConditionalGeneration for both text and mmproj conversion

* fix proper mapping

* Update gguf-py/gguf/tensor_mapping.py

Co-authored-by: Xuan-Son Nguyen <thichthat@gmail.com>

* Update tools/mtmd/clip.cpp

Co-authored-by: Xuan-Son Nguyen <thichthat@gmail.com>

* address comments

* update

* Fix typecheck

* Update convert_hf_to_gguf.py

Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@scala.com>

* Update convert_hf_to_gguf.py

Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@scala.com>

* Update convert_hf_to_gguf.py

Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@scala.com>

* Update convert_hf_to_gguf.py

Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@scala.com>

---------

Co-authored-by: Xuan-Son Nguyen <thichthat@gmail.com>
Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@scala.com>

* llama : correct platform-independent loading of BOOL metadata (ggml-org#21428)

* model-loader : fix GGUF bool array conversion

* model-loader : fix remaining GGUF bool pointer uses

* hexagon: slight optimization for argosrt output init (ggml-org#21463)

* sycl : handle other FA case (ggml-org#21377)

* convert : set "add bos" == True for Gemma 4 (ggml-org#21500)

* convert : set "add bos" == True for Gemma 4

* cont : handle old GGUFs

* docs: add hunyuan-ocr gguf, also add test [no ci] (ggml-org#21490)

* server : handle unsuccessful sink.write in chunked stream provider (ggml-org#21478)

Check the return value of sink.write() in the chunked content provider
and return false when the write fails, matching cpp-httplib's own
streaming contract. This prevents logging chunks as sent when the sink
rejected them and properly aborts the stream on connection failure.

* convert : fix block_ff_dim retrieval for lfm2 (ggml-org#21508)

* vocab : add byte token handling to BPE detokenizer for Gemma4 (ggml-org#21488)

* llama-bench: add `-fitc` and `-fitt` to arguments (ggml-org#21304)

* llama-bench: add `-fitc` and `-fitt` to arguments

* update README.md

* address review comments

* update compare-llama-bench.py

* [CUDA ] Write an optimized flash_attn_stream_k_fixup kernel (ggml-org#21159)

* Write an optimized flash_attn_stream_k_fixup kernel

Write a specialized and more optimized kernel for cases where nblocks_stream_k is multiple of ntiles_dst.
Make nblocks_stream_k to multiple of ntiles_dst if nblocks_stream_k > 2 * ntiles_dst

* Use the new kernel only for nblocks_stream_k_raw > 4 * ntiles_dst to make sure we have enough concurrency on GPUs

* Address review comments

* Address review comments

* Revert variable names to original

* cli: fix stripping of \n in multiline input (ggml-org#21485)

* llama-cli: fix stripping of \n in multiline input

* Change & string to string_view

* Apply suggestions from code review

Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@scala.com>

* Fix EditorConfig linter error

---------

Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@scala.com>

* ggml: add Q1_0 1-bit quantization support (CPU) (ggml-org#21273)

* ggml: add Q1_0 and Q1_0_g128 1-bit quantization support (CPU)

* add generic fallback for x86

* remove Q1_0 (group size 32)

* rename Q1_0_g128 => Q1_0

* fix Q1_0 LlamaFileType Enum

* Fix trailing spaces; add generic fallback for othre backends

* Apply suggestions from code review

Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@scala.com>

* fix /r/n spacing + arch-fallback

---------

Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@scala.com>

* ggml-webgpu: Add the support of `MUL_MAT_ID` (ggml-org#21147)

* Add mul_mat_id support to WebGPU

* Apply suggestion from @reeselevine

---------

Co-authored-by: Reese Levine <reeselevine1@gmail.com>

* docs: fix typo in build.md (emdawbwebgpu -> emdawnwebgpu) (ggml-org#21518)

* [SYCL] Add Q8_0 reorder optimization (~3x tg speedup on Intel Arc) (ggml-org#21527)

Extend the existing reorder optimization to Q8_0. The reorder
separates scale factors from weight data for coalesced memory
access -- was implemented for Q4_0/Q4_K/Q6_K but Q8_0 was missing.

On Arc Pro B70 (Xe2), Q8_0 tg goes from 4.88 to 15.24 t/s (3.1x)
on Qwen3.5-27B. BW utilization: 21% -> 66%.

The key fix beyond the kernels: Q8_0 was missing from the type
check in ggml_backend_sycl_buffer_init_tensor() that allocates
the extra struct carrying the reorder flag -- so the optimization
was silently skipped.

AI (Claude) was used to assist with root cause investigation and
writing the kernel code. All code was human-reviewed and tested
on real hardware.

Fixes: ggml-org#21517

* Fix rtl text rendering (ggml-org#21382)

* Fix Arabic RTL text rendering in web UI

- Add dir='auto' attributes to markdown containers and blocks
- Implement post-processing to add dir='auto' to all text elements
- Replace directional CSS properties with logical properties for proper RTL list alignment
- Ensure bidirectional text support for mixed Arabic/English content

* Clean up commented duplicate function

Remove the commented-out duplicate transformMdastNode function
that was left over from refactoring.

* Fix Arabic RTL text rendering in web UI

- Add dir='auto' attributes to markdown containers and blocks
- Implement post-processing to add dir='auto' to all text elements
- Replace directional CSS properties with logical properties for proper RTL list alignment
- Minor code formatting improvements

This ensures bidirectional text support for mixed Arabic/English content in the llama.cpp web UI.

* Implement rehype plugin for comprehensive RTL text support

- Add rehypeRtlSupport plugin that applies dir='auto' to all elements with children
- Replace DOMParser-based approach with efficient HAST tree processing
- Remove hardcoded element lists for better maintainability
- Ensure proper bidirectional text rendering for mixed RTL/LTR content

* Fix RTL text rendering with rehype plugin and cleanup

* fix: prettier formatting

* fix: Detect streaming state in reasoning content blocks (ggml-org#21549)

* ggml-cuda : fix CDNA2 compute capability constant for gfx90a (MI210) (ggml-org#21519)

GGML_CUDA_CC_CDNA2 was set to 0x910
Fix by setting the constant to 0x90a to match the actual gfx90a ISA.

* webui : store reasoning_content so it is sent back in subsequent requests (ggml-org#21249)

* vulkan: add FA dequant for q4_1, q5_0, q5_1, iq4_nl (ggml-org#21029)

Add dequantize4() implementations for Q4_1, Q5_0, Q5_1, and IQ4_NL
in the flash attention base shader. Register them in the shader
generator, pipeline creation, and enable in the scalar/coopmat1 FA
support check.

* ggml: Vulkan build, Linux -- output error string for errno on fork failure (ggml-org#20868) (ggml-org#20904)

* ggml : deprecate GGML_OP_ADD1 (ggml-org#21363)

* ggml : deprecate GGML_OP_ADD1

* cont : remove tests

* cont : re-enable vulkan check

* server : fix restore for checkpoints with pos_min == 0 (ggml-org#21510)

* llama: remove per-arch tensor name lists (ggml-org#21531)

* unicode : add custom Qwen2 regex handler to fix segfault on long input (ggml-org#21257)

* unicode : add custom Qwen2 regex handler to fix segfault on long input

std::regex uses recursive backtracking internally, which causes a stack
overflow (segfault) when tokenizing long sequences of repeated characters
(e.g. 43K 'A's). The Qwen2 tokenizer regex differs from Llama3 only in
the digit pattern (\p{N} vs \p{N}{1,3}), so it was falling through to
the std::regex fallback path instead of using a custom handler.

Add unicode_regex_split_custom_qwen2() following the established pattern
used by gpt2, llama3, kimi_k2, and afmoe custom handlers.

Closes: ggml-org#21113

* cont : remove TODO comment

* cont : update comment to reflect original regex

* use the correct regex in the comment this time... [no ci]

---------

Co-authored-by: Aldehir Rojas <hello@alde.dev>

* llama-server: fix model params not propagated (ggml-org#21509)

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* CUDA: check for buffer overlap before fusing (ggml-org#21566)

* CUDA: check for buffer overlap before fusing

* use ggml_cuda_check_fusion_memory_ranges

* ggml-webgpu: parameterize submission size and add iOS specific limits (ggml-org#21533)

* Work towards removing bitcast

* Move rest of existing types over

* Add timeout back to wait and remove synchronous set_tensor/memset_tensor

* move to unpackf16 for wider compatibility

* cleanup

* Remove deadlock condition in free_bufs

* Start work on removing parameter buffer pools

* Simplify and optimize further

* simplify profile futures

* Fix stride

* Try using a single command buffer per batch

* formatting

* Add parameters for different browsers in-flight submissions

* Update handling of batch size too

* Throttle ios as much as possible

* Increase timeout for llvm-pipe testing

* kv-cache : support attention rotation for heterogeneous iSWA (ggml-org#21513)

* kv-cache : support attention rotation for heterogeneous iSWA

* cont : remove assert

* gguf-py : fix missing comma after bad merge in tensor-mapping (ggml-org#21558)

This commit adds a missing comma in the vision encoder attention qkv
block.

The motivation for this change is that without the comma there will be
a string concatenation of the Kimi-K2.5 and the Nemotron Nano v2 VL
tensor mappings which will be broken.

* ggml-cuda: ds_read_b128 for q4_0 and q4_1 mmq kernels (ggml-org#21168)

* ds_read_b128 for q4_0 and q4_1 mmq kernels

     Current for loop generates ds_read_b32 instructions with hip compiler, the new solution generates ds_read_b128 instructions for the same operation, saving some LDS bandwidth. Tested on MI50 and RX6800XT, its faster on both.

* Vectorized lds load update: used ggml_cuda_get_max_cpy_bytes and ggml_cuda_memcpy_1 functions for generic implementation

* Explicit for loop in mmq, renamed vec into tmp

* Fixed max_cpy usage in the loading loop

* Fixed typo in q4_1 kernel

* Update ggml/src/ggml-cuda/mmq.cuh

Co-authored-by: Johannes Gäßler <johannesg@5d6.de>

* Update ggml/src/ggml-cuda/mmq.cuh

Co-authored-by: Johannes Gäßler <johannesg@5d6.de>

* Update ggml/src/ggml-cuda/mmq.cuh

Co-authored-by: Johannes Gäßler <johannesg@5d6.de>

* Renoved trailing white line 500

* Update mmq.cuh removed other whitelines

* Remove trailing whitespaces

---------

Co-authored-by: iacopPBK <iacopPBK@users.noreply.github.com>
Co-authored-by: Johannes Gäßler <johannesg@5d6.de>
Co-authored-by: iacopPBK <iacop@deneb.com>

* CUDA: make cuda graphs props check faster (ggml-org#21472)

* CUDA: compute fast hash instead of expensive props check

* use seen node

* use memcp

* devops: kleidiai: provide KleidiAI-Enabled ARM Release Artifact (ggml-org#21259)

* Unified macOS release setup with strategy-matrix block
 * Added KleidiAI arm64 macOS release definition


Change-Id: I05520889ffc646488a178d06817a17f29274465a

Signed-off-by: Martin Klacer <martin.klacer@arm.com>

* webui: fix syntax highlighting lost after streaming for non-common languages (ggml-org#21206)

* webui: fix syntax highlighting lost for non-common languages after streaming

rehype-highlight uses lowlight internally, which only bundles 37 "common"
languages. The streaming code path uses highlight.js directly (192 languages),
so languages like Haskell highlight correctly while streaming but lose all
color once the code block closes. Pass the full lowlight language set to
rehype-highlight so both paths support the same languages.

* webui: rebuild static files after rebase

* model : support step3-vl-10b (ggml-org#21287)

* feat: support step3-vl-10b

* use fused QKV && mapping tensor in tensor_mapping.py

* guard hardcoded params and drop crop metadata

* get understand_projector_stride from global config

* img_u8_resize_bilinear_to_f32 move in step3vl class

* Apply suggestions from code review

Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@scala.com>

* fix the \r\n mess

* add width and heads to MmprojModel.set_gguf_parameters

---------

Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@scala.com>

* chore: Remove legacy files (ggml-org#21606)

* chore: Update labeler to have separate labels for `server/webui` and `server` changes (ggml-org#21567)

* tests : remove obsolete .mjs script (ggml-org#21615)

* parser: fix MiniMax handling (ggml-org#21573)

* examples : disable cb_eval callback for --save-logits (ggml-org#21553)

This commit updates the debug example to not create the
base_callback_data.

The motivation for this is when using `--save-logits`, which is used by
examples/model-conversion scripts, we often don't care about the tensor
outputs and they just add noise to the output. This changes is quiet by
default we can always remove --save-logits to get the tensor outputs
when debugging.

* gemma : perform per-layer projections in the first layer (ggml-org#21612)

* gemma : reduce graph splits by keeping per-layer ops in the input layer

* gemma : put the per-layer proj in the first layer

* cont : move the projection before the layer loop

* metal: Q1_0 backend (ggml-org#21528)

* initial Q1_0 Metal backend

* tuning q1_0 metal kernels

* add Q1_0 to test-backend-ops

* add Q1_0<->F32 copy test

* Apply suggestions from code review

Co-authored-by: Georgi Gerganov <ggerganov@gmail.com>

---------

Co-authored-by: Georgi Gerganov <ggerganov@gmail.com>

* webgpu : Query for adapter support when registering WebGPU backend (ggml-org#21579)

* kv-cache : extend cache quantization checks (ggml-org#21586)

to also check for enabled flash attention, instead of just auto.

* Propose fix a couple of typos (ggml-org#21581)

Signed-off-by: John E <jeis4wpi@outlook.com>

* webui : send both backend_sampling == false/true (ggml-org#18781)

* webui : send both backend_sampling == false/true

* feat: Parameter sync

---------

Co-authored-by: Aleksander Grygier <aleksander.grygier@gmail.com>

* vocab : remove </s> eog token if gemma4 (ggml-org#21492)

* server: respect the ignore eos flag (ggml-org#21203)

* fix: free ctx_copy in ggml_opt_free to plug per-training-session leak (ggml-org#21592)

* fix: free ctx_copy in ggml_opt_free to plug per-training-session leak

ggml_opt_alloc populates opt_ctx->ctx_copy via a free+init pair every
time the allocated graph shape changes. The last ctx_copy from the
final ggml_opt_alloc call survives until ggml_opt_free is invoked,
but ggml_opt_free was only freeing ctx_static and ctx_cpu, never
ctx_copy. Each opt_ctx lifetime therefore leaks the final per-batch
context — ~900 KB for a typical GNN training session in
sindarin-pkg-tensor, surfaced via AddressSanitizer.

ctx_copy is nullptr-initialized and ggml_free() handles NULL safely,
so the new release is guard-free.

* Update ggml/src/ggml-opt.cpp

Co-authored-by: Johannes Gäßler <johannesg@5d6.de>

---------

Co-authored-by: realorko <realorko@nowhere.com>
Co-authored-by: Johannes Gäßler <johannesg@5d6.de>

* CUDA: also store `node->src->data` ptrs for equality check (ggml-org#21635)

* CUDA: also store node->src->data ptrs for equality check

* address review comments

* common : skip non-primary GGUF split files when selecting model (ggml-org#21633)

We should not assume files are listed in order.

Signed-off-by: Adrien Gallouët <angt@huggingface.co>

* vulkan: unify type macros to use Vx instead of _VECx (ggml-org#21605)

* ci: drop v5 `all:` composition from labeler.yml (ggml-org#21627)

actions/labeler@v6 removed the `all:` / `any:` composition keys.
The `server/webui` and `server` entries used `all:` to combine
`any-glob-to-any-file` with negated `all-globs-to-all-files`,
which now errors on every PR with:

    Unknown config options were under "changed-files": all

Flatten both entries to a single `any-glob-to-any-file`. PRs
touching both webui and other server files will now receive both
labels instead of only `server/webui`.

Co-authored-by: Marxist-Leninist <noreply@users.noreply.github.com>

* sycl : add flash-attn support for head size 512 (ggml-org#21654)

* sycl : add flash-attn support for head size 512

This patch extends the SYCL Flash Attention implementation to support head sizes (DKQ/DV) of 512.

Changes:
- Added DKQ/DV 512 cases to both tile and vector Flash Attention kernels.
- Updated kernel selection logic to allow vector kernels for head sizes up to 512 (previously 256).
- Removed unused/redundant AMD and RDNA-specific configuration functions in `fattn-tile.hpp`.
- Refactored `ggml_backend_sycl_buffer_init_tensor` to use a switch statement for clearer tensor extra buffer initialization.
- Added necessary template instances for the new 512 head size across various quantization types.

* remove defunct mxfp4 reorder from setting buffer type

* webui: Add option to pre-encode conversation for faster next turns (ggml-org#21034)

* server : fix grammar commandline args (ggml-org#21543)

Co-authored-by: AUTOMATIC <->

* fix: Model Selector choice sync (ggml-org#21628)

* metal : add missing mm-id specializations for q1_0 (ggml-org#21662)

* jinja : support ensure_ascii=true, string repetition and int/float self-filtering (ggml-org#21623)

* feat: jinja engine improvements for reka-edge

Port three Jinja engine improvements needed for the reka-edge model:
1. Python-style string repetition ("ab" * 3 → "ababab")
2. ensure_ascii=true support for tojson filter (escapes non-ASCII to \uXXXX)
3. int() builtin on value_int_t (identity, needed for Reka Edge template)

* fix: escape invalid utf8 bytes when ensure_ascii=true

The json_ensure_ascii_preserving_format function does not correctly
handle an edge case where if UTF-8 parsing fails, it adds the non-ascii
character back to the output as a raw byte.

This commit fixes that by adding the unicode standard replacement
character \\ufffd to the output instead. This is the standard behavior
for various programming languages like Python, Rust, Go, etc.

* chore: address PR comments

1. Add todo comment for supporting string repetition for array/tuples
2. Add support for float identity operation
3. Move invalid ascii test case to test_fuzzing

* chore: accept suggestion for common/jinja/value.cpp

Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@scala.com>

---------

Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@scala.com>

* vocab: add gemma4 tokenizer tests, fix edge case (ggml-org#21534)

* YATF (Yet Another Tokenizer Fix) for Gemma 4. With tests!
* Remove unnecessary hash  from update script.
* minor: move constant

* mtmd: support dots.ocr (ggml-org#17575)

* convert gguf

* clip impl

* fix conversion

* wip

* corrections

* update docs

* add gguf to test script

* model: fix multimodal padding token for gemma3n/gemma4 (ggml-org#21625)

* model: fix multimodal padding token for gemma3n/gemma4

* nits

* common : simplify autoparser tagged parser rules (ggml-org#21216)

* common : simplify autoparser tagged parser rules

* cont : remove upper limit on optional args

* cont : revert changes to parsing at the end

* cont : undo arbitrary ordering of optional args

* cont : fix uninitialized required parameters

* revert to simplify merge

* re-apply patches

* restore flexible optional arg ordering tests

* common : fix ambiguous grammar rule in gemma4 (ggml-org#21661)

* common : fix ambiguous grammar rule in gemma4

* cont : fix missing comma...

* webui: add "Send message on Enter" setting (ggml-org#21577)

* webui: make Enter to send chat a setting

* Shorten description

* Use isMobile hook from $lib/hooks

* Rebuild static output

* requirements : update transformers to 5.5.1 (ggml-org#21617)

* requirements : update transformers to 5.5.0

This commit updates the transformers dependency to version 5.5.0.

The motivation for this is that transformers 5.5.0 includes support for
Gemma4 and is required to be able to convert Gemma4 models. This is also
causing issues for user of gguf-my-repo.

Refs: https://huggingface.co/spaces/ggml-org/gguf-my-repo/discussions/202

* fix huggingface_hub version

* set version of transformers to 5.5.0

* convert : add ty ignore directives to convert_hf_to_gguf.py

This commit adds `ty: ignore` directives to transformers tokenizers
field/methods to avoid type check errors. There might be better ways to
handle this and perhaps this can be done in a follow up commit.

The motivation for this is that it looks like in transformers 5.5.0
AutoTokenizer.from_pretrained can return generic tokenizer types or None
and the type checker now produces an error when the conversion script
accesses field like tokenizer.vocab.

* convert : add ty ignore to suppress type check errors

* convert : remove incorrect type ignores

* convert : fix remaining python checks

I was running a newer version of ty locally but I've switched to
version 0.0.26 which is what CI uses and I was then able to reproduce
the errors. Sorry about the noise.

* update transformers version to 5.5.1

* ggml : check return value of CUB calls used in argsort and top-k (they all return cudaError_t) (ggml-org#21676)

Co-authored-by: Stanisław Szymczyk <sszymczy@gmail.com>

* ggml: backend-agnostic tensor parallelism (experimental) (ggml-org#19378)

* ggml: backend-agnostic tensor parallelism

* support for GPT-OSS, Qwen 3 MoE

* partial Vulkan fix

* add support for 4/8 GPUs

* unconditional peer access

* re-use buffers + ggml contexts

* fix output pattern

* NCCL support

* GGML: HIP: add RCCL support

* Remove shfl and AllReduce from backend interface

* move allocation workaround out of ggml-alloc.c

* 2d tensor set/get support

* Fix the seg fault without NCCL

* Apply suggestion from JohannesGaessler

* support for tensor dims % n_devs != 0

* fix view_offs scaling

* arbitrary num. of GPUs/tensor split

* fix compilation

* better granularity estimate

* Support device-specific host buffer types if all underlying backends expose the same type. This allows using pinned memory instead of pageable memory for CUDA.

Fix compilation errors.

* partial Qwen 3 Next support

* Fix qwen3 30b (ggml-org#8)

* Fix crash with Qwen-30B-A3B Q4_0

Qwen-30B-A3B Q4_0 has an intermediate dimension of 768. Using a granularity of 256 forces an uneven split between GPUs, which is not supported by the current implementation.

* Decide block size based on tensor quantization type

* Fix crashes due to KV cache serialization (ggml-org#9)

KV cache serialization requires non-zero offsets on the tensor. Add support in the meta backend to set/get a tensor with a non-zero offset.

* metal : fix build (ggml-org#7)

* static memory allocations, fix usage count

* fix tensor granularity

* more even memory distribution

* use BF16 for allreduce

* rebase fixup

* better error message for unsupported architectures

* Fix device mismatch during scatter of allReduce. (ggml-org#11)

There is a mismatch between the dst buffer device and the backend device, causing the use of sync copies

* Enable the previous allreduce implementation. It is better in both perf and stability (ggml-org#12)

* delay AllReduce for Moe for less I/O

* build : clean-up compile warnings

* backend : move most of the meta backend API to ggml-backend-impl.h

* cont : hide unused public API in the implementation

* llama : use llama_device + remove ggml_backend_dev_is_meta()

* ggml-backend : remove unused alloc include

* minor : remove regex include

* ggml : introduce ggml-ext.h for staging new APIs

* rebase fixup

* fix tests

* llama : more robust logic for determining Meta devices (ggml-org#16)

* llama : more robust logic for determining Meta devices

* cont : fix devs size check

Co-authored-by: Johannes Gäßler <johannesg@5d6.de>

* cont : fix log type

Co-authored-by: Johannes Gäßler <johannesg@5d6.de>

---------

Co-authored-by: Johannes Gäßler <johannesg@5d6.de>

* disable roundtrip for meta backend

* fix arch selection

* Qwen 3.5 support

* fix Gemma 4 MoE

* fix OpenVino, SYCL

* fix test-llama-archs for CPU-only builds

* Fix Qwen 3.5 MoE

* disable meta backend tests for WebGPU

* tests : filter CPU-based devices from the Meta backend tests (ggml-org#17)

* meta : formatting, naming, indentation (ggml-org#18)

* formatting : llama-model.cpp

* formatting : ggml-ext.h

* formatting : ggml-backend-meta.cpp

* meta : add TODO

* add documentation

* better error messages

* fix GPT-OSS

---------

Co-authored-by: Carl Philipp Klemm <carl@uvos.xyz>
Co-authored-by: Gaurav Garg <gaugarg@nvidia.com>
Co-authored-by: Georgi Gerganov <ggerganov@gmail.com>

* HIP: add CDNA4 (gfx950) architecture support for MI350X/MI355X (ggml-org#21570)

Add AMD Instinct MI350X/MI355X (gfx950, CDNA4) support:

- vendors/hip.h: Add CDNA4 preprocessor define for __gfx950__
- common.cuh: Add GGML_CUDA_CC_CDNA4 and GGML_CUDA_CC_IS_CDNA4 macros
- mma.cuh: Route CDNA4 to compatible MFMA instructions:
  * f32 matmul: mfma_f32_16x16x4f32 (xf32 variant unavailable on gfx950)
  * bf16 matmul: mfma_f32_16x16x16bf16_1k (same as CDNA3)
  * int8 matmul: mfma_i32_16x16x32_i8/32x32x16 (same as CDNA3)
- mmq.cuh: Include CDNA4 in stream-k kernel dispatch

CDNA4 is largely compatible with CDNA3 except:
- No xf32 MFMA (mfma_f32_16x16x8_xf32) — routes to f32 path
- Different FP8 format (e4m3fn vs e4m3_fnuz) — not changed here

Tested on AMD Instinct MI355X (gfx950), ROCm 7.0.1:
- Build: compiles cleanly with -DAMDGPU_TARGETS=gfx950
- llama-bench (Qwen2.5-1.5B Q4_K_M, single GPU):
  * f16+FA: 40,013 tok/s prefill, 254 tok/s decode
  * q8_0+FA: functional
- Flash attention: works correctly
- MMQ: works correctly with stream-k dispatch

Co-authored-by: Andy Luo <andyluo7@users.noreply.github.com>

* CUDA: fuse muls (ggml-org#21665)

* common : add fluidity to the progress bar (ggml-org#21671)

Signed-off-by: Adrien Gallouët <angt@huggingface.co>

* vulkan: Support Q1_0 (ggml-org#21539)

* vulkan: Support Q1_0

* use get_dm

* docs : fix broken link to ggml-openvino in OPENVINO.md (ggml-org#21709)

* common : enable reasoning budget sampler for gemma4 (ggml-org#21697)

* fix: enable reasoning budget sampler for gemma4

Add thinking_start_tag and thinking_end_tag to
common_chat_params_init_gemma4(). Without these, the reasoning
budget sampler never activates for gemma4.

Make the newline after "thought" optional in the PEG parser to
handle budget=0 (sampler forces end tag before the newline).

Add test case for empty thinking block.

Fixes ggml-org#21487

* use p.space() instead of p.optional(p.literal("\n")) in gemma4 thought parser

* webui: Static build output improvements (ggml-org#21667)

* refactor: Build improvements

* chore: Formatting + package lock update

* common: mark --split-mode tensor as experimental (ggml-org#21684)

* common : fix when loading a cached HF models with unavailable API (ggml-org#21670)

Signed-off-by: Adrien Gallouët <angt@huggingface.co>

* server : ignore --alias when using --models-preset (ggml-org#21380)

I'm not sure what the purpose of keeping `--alias` was when using
`--models-preset`, but the result is really weird, as shown in the
following logs:

    $ build/bin/llama-server --models-preset preset.ini --alias "Gemma 4 E4B UD Q8_K_XL"
    ...
    init: using 31 threads for HTTP server
    srv   load_models: Loaded 2 cached model presets
    srv   load_models: Loaded 1 custom model presets from preset.ini
    main: failed to initialize router models: alias 'Gemma 4 E4B UD Q8_K_XL' for model 'angt/test-split-model-stories260K:F32' conflicts with existing model name

So I propose to simply ignore `--alias` too in this case. With this
commit, the server starts in routing mode correctly.

Signed-off-by: Adrien Gallouët <angt@huggingface.co>

* ggml-webgpu: address quantization precision and backend lifecycle managment (ggml-org#21521)

* ggml(webgpu): fix the busy-polls in Emscripten  in the waitAny after ggml-org#20618, and remove the busy webgpu log

* Merge with upstream

* Fix GET_ROWS packed integer NaN when using f16 as memory buffer in shader quants

* Update Unary wgsl EXP and EXPM1 for f16 stability

* Fix GET_ROWS IQ4_XS strcut for NaN f16 canonicalization

* Fix numerical percision for unary sqrt when working with f16

* Fix NaN canonicalization for packed integers using f16

* Update err threshold for binary div ops when using f16

* backend: Keep one Dawn/WebGPU instance alive for the lifetime of the static backend

* clean: uncomment existing code logs

* clean: clean the unncessary debug info

* Refactor and generalize dequant helpers

* Remove deprecated quant structs

* Refactor shader defines to reduce repetition

* Remove error override for F16 type

* fix: fix the accidential removal of the proper initialization of ctx

* clean: clean legacy and format code

* fix: did not modify tests ops

---------

Co-authored-by: Jeremy J. Hartmann <jeremy@mtion.tv>

* ggml-webgpu: support non-square subgroup matrix configs for Intel GPUs (ggml-org#21669)

* model : make Gemma 4 shared-KV tail attn_k tensors optional on load (ggml-org#21739)

* common : add callback interface for download progress (ggml-org#21735)

Signed-off-by: Adrien Gallouët <angt@huggingface.co>

* common : better align to the updated official gemma4 template (ggml-org#21704)

* hexagon: improved Op queuing, buffer and cache management (ggml-org#21705)

* hexagon: introduce op request batching and rewrite buffer managment

The host now prepares batches of requests and dispatches them via a single dspqueue message.

Buffers are mapped explicitly by NPU while processing batches.

* hex-dma: disable l2 bypass since to work around new issue due to no flushes between Ops

* hex-utils: add explicit l2flush and l2clear helpers

* hex-opreq: use fine-grain per tensor l2 management

* hex-opreq: avoid redundant invalidates for tensors we already flushed

* hex-opreq: update debug messages

* htp-opreq: reuse ops_context

* hex-opreq: do not flush or invalidate cache lines beyond buffer boundry

* hex-opreq: fix errors in log message

* Revert "hex-opreq: do not flush or invalidate cache lines beyond buffer boundry"

This reverts commit 8b7f0a55a750a6430ce4eb1874c7feb3d720056d.

* hexagon: limit l2 flushes to 1MB which covers l2 cache

* hex-opreq: limit cache flush to 4MB

Looks like 4MB cont. vitual space should cover the 1MB cache.

* hexagon: drop cache flush size to 2MB

* hex-opreq: start reworking opreq packing

* hex-opreq: introduce new way of packing opbatch where tensors are stored separately

* hex-opreq: add a simple fastrpc call to force unmap all buffers

* hex-l2flush: somehow 2MB does not seem robust, also cleanup step size to use line-size

* hex-opreq: bump opreq batch size to 256

* hex-mm: place src1 spad at the top of vtcm for easy reuse

* hex-ops: introduce internal types and disable src1 reuse for now

Nothing new just formalizing the repack / qyn.quant types we've been using.

* htp-opreq: use tensor pointers instead of copies

* hex-opreq: introduce more robust way for tracking vtcm/spad reuse

This removes the SKIP_QUANTIZE flag that became fragile with the addition of HMX and other ops.

* hex-cumsum: fix error post opreq merge

* hex-opreq: move request batch handling into the session

Prepping everything for using dspqueue buffers and doing that inside the session is much cleaner.

* hex-mm: yet another fix for src1 reuse when we're mixing hmx/hvx

* hex-bufs: introduce pinned mmapings and use non-pinned ones for model buffers

* hex-buf: add support for allocating shared/pinned buffer for opreqs

* hex-opbatch: make opbatches configurable

* hex-naming: better name for ggml_hexagon_shared_buffer

* hex-naming: add session->c_name() helper

* hex-opbatch: start using shm but still copy for now

* hex-opbatch: use shared buffer for packing opbatch

* hex-opbatch: beter naming for opbatch related classes and code

* hex-opbatch: reuse batched tensors with same data/dims/strides

* hex-opbatch: update logging

* hex-opbatch: add support for vmem limit for op batching

* hex-opbatch: update htp side to properly support dynamic mmap/unmap

* hex-opbatch: add OB and OQ params for run-completion script and fix the asserts in batch processing

* hex-opbatch: fixed src1 handling in act ops

* hex-act: fix empty src1 handling in swiglu and friends

Simplify preamble macro while at it

* hex-mm: minor fix vtcm and dma handling in matmul

cleaning up some left-overs from merges

* hex-opbatch: allocate extra 1KB for dspqueue overhead

* hexagon: fix softmax for non-aligned tensors and cleanup vtcm alloc

* hex-mm: properly handle hmx_disabled flag

* hex-ops: update comments

* hex-ops: add debug output for get/set-rows

* hex-mmap: optimize un/mapping of buffers

* hex-opreq: global cache flush and invalidate beyond 128KB threshold

* hex-ops: add super simple opfilter regex for debugging

If an Op matches the regex hex backend will reject it.

* hex-opbatch: wireup newer ops missed in merge and update main switch to detect this in future

* hexagon: improved vtcm acquision to remove inter-op overhead

Fully compatible with QNN-HTP coex

* hex-mm: fixed hvx fallback path

* hex-mm: lower the vmem threshold a bit further to ~3GB

* hexagon: update debug & error logs

This also fixes an issue with newer llvm merging repack and non-repack
functions. We use those pointer to distinguish between buffer types.

* hexagon: move ops context into main context

Just a cleanup. We don't need separate contexts at this point.

* hex-opbatch: cleanup naming and headers for opbatch and related descriptors

* hex-fa: it's now better to enable FA during TG to reduce graph splits

* hexagon: remove GGML_HEXAGON_EXPERIMENTAL env var

It's no longer useful. Please use more flexible GGML_HEXAGON_OPFILTER to disable Ops
if needed for debugging or validation.

* hexagon: fixed editorconfig check

* Update ggml/src/ggml-hexagon/ggml-hexagon.cpp

Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@scala.com>

---------

Co-authored-by: Trivikram Reddy <tamarnat@qti.qualcomm.com>
Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@scala.com>

* hexagon: add support for linux on snapdragon (ggml-org#21707)

* hexagon: add support for debian on ex2

* hexagon: add -fvectotize to c/c++ cmake flags

* hexagon: remove trailing white space

* update onboarding steps

* hexagon: update linux setup documentation

* hexagon: update intallation scripts

* Hexagon: update docs

* hexagon: update onboarding scripts

---------

Co-authored-by: Zack Li <zackli@qti.qualcomm.com>

* fix: Fix broken structured output when using $refs in json_schema (ggml-org#21699)

* CUDA: also store node->src ne/nb for graph equality (ggml-org#21736)

* py : Bump typer to latest to fix huggingface_hub issue (ggml-org#21701)

* ggml : fix a few instances of missing GGML_TYPE_Q1_0 cases (ggml-org#21716)

* TP: fix Qwen 3 Next data split (ggml-org#21732)

* opencl: add basic support for q5_k (ggml-org#21593)

* opencl: add general q5_k mv

* opencl: add flattened Q5_K mv and general Q5_K mm

* opencl: fix Q5_K unit tests

---------

Signed-off-by: Adrien Gallouët <angt@huggingface.co>
Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>
Signed-off-by: Martin Klacer <martin.klacer@arm.com>
Signed-off-by: John E <jeis4wpi@outlook.com>
Co-authored-by: Xuan-Son Nguyen <son@huggingface.co>
Co-authored-by: Ruben Ortlam <rortlam@redhat.com>
Co-authored-by: Zheyuan Chen <sephirotheca17@gmail.com>
Co-authored-by: Reese Levine <reeselevine1@gmail.com>
Co-authored-by: Bartowski <3266127+bartowski1182@users.noreply.github.com>
Co-authored-by: Georgi Gerganov <ggerganov@gmail.com>
Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@scala.com>
Co-authored-by: Piotr Wilkin (ilintar) <piotr.wilkin@syndatis.com>
Co-authored-by: Slobodan Josic <127323561+slojosic-amd@users.noreply.github.com>
Co-authored-by: Vishal Singh <vishal@zettabolt.com>
Co-authored-by: Aaron Teo <taronaeo@gmail.com>
Co-authored-by: Radoslav Gerganov <rgerganov@gmail.com>
Co-authored-by: sayap <sokann@gmail.com>
Co-authored-by: Tillerino <Tillerino@users.noreply.github.com>
Co-authored-by: uvos <carl@uvos.xyz>
Co-authored-by: Aaron Teo <aaron.teo1@ibm.com>
Co-authored-by: jeromew <jerome.wagner@m4x.org>
Co-authored-by: M1DNYT3 <42499082+M1DNYT3@users.noreply.github.com>
Co-authored-by: M1DNYT3 <m1dnyt3@MacBookPro.lan>
Co-authored-by: CISC <CISC@users.noreply.github.com>
Co-authored-by: Samanvya Tripathi <samanu09@gmail.com>
Co-authored-by: Yes You Can Have Your Own <188969017+yychyo@users.noreply.github.com>
Co-authored-by: Masato Nakasaka <masato.nakasaka@intel.com>
Co-authored-by: Aman Gupta <amangupta052@gmail.com>
Co-authored-by: SamareshSingh <97642706+ssam18@users.noreply.github.com>
Co-authored-by: Adrien Gallouët <angt@huggingface.co>
Co-authored-by: Dan Hoffman <43101339+thedanhoffman@users.noreply.github.com>
Co-authored-by: Dan Hoffman <dhoffman@cyket.net>
Co-authored-by: Aldehir Rojas <hello@alde.dev>
Co-authored-by: Nicholas Sparks <157740354+nisparks@users.noreply.github.com>
Co-authored-by: ddh0 <chemist-mulches-39@icloud.com>
Co-authored-by: Ludovic Henry <ludovic@rivosinc.com>
Co-authored-by: Richard Davison <richard.davison1@gmail.com>
Co-authored-by: Xuan-Son Nguyen <thichthat@gmail.com>
Co-authored-by: anchortense <daniel.redshaw@uqconnect.edu.au>
Co-authored-by: Yarden Tal <yardent@qti.qualcomm.com>
Co-authored-by: Neo Zhang <zhang.jianyu@outlook.com>
Co-authored-by: lainon1 <271530700+lainon1@users.noreply.github.com>
Co-authored-by: Gaurav Garg <gaugarg@nvidia.com>
Co-authored-by: Bipin Yadav <83943505+bipinyadav3175@users.noreply.github.com>
Co-authored-by: Pasha Khosravi <khosravipasha@users.noreply.github.com>
Co-authored-by: Masashi Yoshimura <yoshimura.masashi.frbs@gmail.com>
Co-authored-by: Dmytro Romanov <casteldazur@gmail.com>
Co-authored-by: PMZFX <georgiopapairo@gmail.com>
Co-authored-by: Kabir08 <62639358+Kabir08@users.noreply.github.com>
Co-authored-by: Aleksander Grygier <aleksander.grygier@gmail.com>
Co-authored-by: Antoine Viallon <antoine@lesviallon.fr>
Co-authored-by: mkoker <132301062+mkoker@users.noreply.github.com>
Co-authored-by: Tom Overlund <tomov@dilacero.org>
Co-authored-by: Johannes Gäßler <johannesg@5d6.de>
Co-authored-by: Son H. Nguyen <33925625+nhs000@users.noreply.github.com>
Co-authored-by: Daniel Bevenius <daniel.bevenius@gmail.com>
Co-authored-by: iacopPBK <iacopogiottorossi@gmail.com>
Co-authored-by: iacopPBK <iacopPBK@users.noreply.github.com>
Co-authored-by: iacopPBK <iacop@deneb.com>
Co-authored-by: Martin Klacer <martin.klacer@arm.com>
Co-authored-by: Hamish M. Blair <hmblair@stanford.edu>
Co-authored-by: forforever73 <63285796+forforever73@users.noreply.github.com>
Co-authored-by: Erik Scholz <Green-Sky@users.noreply.github.com>
Co-authored-by: John Eismeier <42679190+jeis4wpi@users.noreply.github.com>
Co-authored-by: Yuri Khrustalev <ykhrustalev@users.noreply.github.com>
Co-authored-by: RealOrko <45273739+RealOrko@users.noreply.github.com>
Co-authored-by: realorko <realorko@nowhere.com>
Co-authored-by: Marxist-Leninist <31905382+Marxist-Leninist@users.noreply.github.com>
Co-authored-by: Marxist-Leninist <noreply@users.noreply.github.com>
Co-authored-by: Akarshan Biswas <akarshan@menlo.ai>
Co-authored-by: AUTOMATIC1111 <16777216c@gmail.com>
Co-authored-by: Kwa Jie Hao <31984694+kwajiehao@users.noreply.github.com>
Co-authored-by: JvM <mourix@live.nl>
Co-authored-by: fairydreaming <166155368+fairydreaming@users.noreply.github.com>
Co-authored-by: Stanisław Szymczyk <sszymczy@gmail.com>
Co-authored-by: andyluo7 <43718156+andyluo7@users.noreply.github.com>
Co-authored-by: Andy Luo <andyluo7@users.noreply.github.com>
Co-authored-by: Jeff Bolz <jbolz@nvidia.com>
Co-authored-by: Belem Zhang <belem.zhang@intel.com>
Co-authored-by: Berk Idem <55372926+berkidem@users.noreply.github.com>
Co-authored-by: Chen Yuan <constant.chen@uwaterloo.ca>
Co-authored-by: Jeremy J. Hartmann <jeremy@mtion.tv>
Co-authored-by: Rithik Sharma <rithiksh02@gmail.com>
Co-authored-by: MoonRide303 <130458190+MoonRide303@users.noreply.github.com>
Co-authored-by: Max Krasnyansky <maxk@qti.qualcomm.com>
Co-authored-by: Trivikram Reddy <tamarnat@qti.qualcomm.com>
Co-authored-by: Todor Boinovski <todorb@qti.qualcomm.com>
Co-authored-by: Zack Li <zackli@qti.qualcomm.com>
Co-authored-by: Galunid <karolek1231456@gmail.com>
Co-authored-by: shaofeiqi <shaoqi@qti.qualcomm.com>
slartibardfast pushed a commit to slartibardfast/llama.cpp-prism-avx2 that referenced this pull request Apr 12, 2026
…org#20993)

* server: clear idle slots KV from VRAM (LLAMA_KV_KEEP_ONLY_ACTIVE)

* server: move idle slot KV clearing to slot release

The save "cost" is now paid by the finishing request.

* server: add --kv-clear-idle flag, enable by default

* server: skip clearing last idle slot, clear on launch

* server: test --no-kv-clear-idle flag

* server: simplify on-release clearing loop

* server: remove on-release KV clearing, keep launch-only

* cont : clean-up

* tests: update log strings after --clear-idle rename

* tests: use debug tags instead of log message matching

* test: fix Windows CI by dropping temp log file unlink

---------

Co-authored-by: Georgi Gerganov <ggerganov@gmail.com>
@yychyo

yychyo commented Apr 16, 2026

Copy link
Copy Markdown
Contributor Author

Yes good point @Farmadupe - let's rename.

@ggerganov done:

@yychyo
yychyo deleted the server-kv-keep-only-active branch April 16, 2026 17:01
@kiwixz

kiwixz commented Apr 18, 2026

Copy link
Copy Markdown

I'm afraid this breaks the automatic slot selection algorithm. With this PR all slots but the last are considered empty because slot.prompt.tokens gets cleared, so it only ever uses the same slot.

if (slot.is_processing()) {
continue;
}
const auto & tokens = slot.prompt.tokens;
// skip the slot if it does not contains cached tokens
if (tokens.empty()) {
continue;
}
// fraction of the Longest Common Prefix length with respect to the input prompt length
const float sim_cur = float(tokens.get_common_prefix(task.tokens)) / task.tokens.size();
// select the current slot if the criteria match
if (sim_cur > sim_best && sim_cur > slot_prompt_similarity) {
sim_best = sim_cur;
ret = &slot;
}

@yychyo

yychyo commented Apr 18, 2026

Copy link
Copy Markdown
Contributor Author

I'm afraid this breaks the automatic slot selection algorithm. With this PR all slots but the last are considered empty because slot.prompt.tokens gets cleared, so it only ever uses the same slot.

if (slot.is_processing()) {
continue;
}
const auto & tokens = slot.prompt.tokens;
// skip the slot if it does not contains cached tokens
if (tokens.empty()) {
continue;
}
// fraction of the Longest Common Prefix length with respect to the input prompt length
const float sim_cur = float(tokens.get_common_prefix(task.tokens)) / task.tokens.size();
// select the current slot if the criteria match
if (sim_cur > sim_best && sim_cur > slot_prompt_similarity) {
sim_best = sim_cur;
ret = &slot;
}

@kiwixz - thanks for the feedback! There is a -no-clear-idle option (which is a subject to rename - #20993 (comment) ), you can use it to opt-out of clearing idle slots.

For the behavior change - I assume it's the expected trade-off. Idle slots are freed aggressively so active sequences don't pay attention cost. Content matching still works via the RAM cache.

Also, there was an idea of cooldown period - #20993 (comment) - it could also help (if implemented) in your case.

WDYT?

@ggerganov

Copy link
Copy Markdown
Member

I'm afraid this breaks the automatic slot selection algorithm.

Why do you think it is a problem? Do you experience any performance regressions? The change should be at very least performance neutral and as @yychyo explained, it will improve backends such as CUDA + unified KV cache where we no longer compute the extra cross-sequence attention. So there shouldn't be a reason to use the -no-clear-idle flag.

@kiwixz

kiwixz commented Apr 18, 2026

Copy link
Copy Markdown

Selecting the same slot causes cache trashing, this was made worse by this PR but actually was already a problem before.
I think I found a compatible solution in #22083.

my-other-github-account pushed a commit to my-other-github-account/llama.cpp that referenced this pull request May 15, 2026
…org#20993)

* server: clear idle slots KV from VRAM (LLAMA_KV_KEEP_ONLY_ACTIVE)

* server: move idle slot KV clearing to slot release

The save "cost" is now paid by the finishing request.

* server: add --kv-clear-idle flag, enable by default

* server: skip clearing last idle slot, clear on launch

* server: test --no-kv-clear-idle flag

* server: simplify on-release clearing loop

* server: remove on-release KV clearing, keep launch-only

* cont : clean-up

* tests: update log strings after --clear-idle rename

* tests: use debug tags instead of log message matching

* test: fix Windows CI by dropping temp log file unlink

---------

Co-authored-by: Georgi Gerganov <ggerganov@gmail.com>
fukuro-kun pushed a commit to fukuro-kun/fukuro-llama-cpp-turboquant that referenced this pull request Jul 5, 2026
…org#20993)

* server: clear idle slots KV from VRAM (LLAMA_KV_KEEP_ONLY_ACTIVE)

* server: move idle slot KV clearing to slot release

The save "cost" is now paid by the finishing request.

* server: add --kv-clear-idle flag, enable by default

* server: skip clearing last idle slot, clear on launch

* server: test --no-kv-clear-idle flag

* server: simplify on-release clearing loop

* server: remove on-release KV clearing, keep launch-only

* cont : clean-up

* tests: update log strings after --clear-idle rename

* tests: use debug tags instead of log message matching

* test: fix Windows CI by dropping temp log file unlink

---------

Co-authored-by: Georgi Gerganov <ggerganov@gmail.com>
MrLordCat referenced this pull request in MrLordCat/llama.cpp-rdna-lab Jul 16, 2026
* server: clear idle slots KV from VRAM (LLAMA_KV_KEEP_ONLY_ACTIVE)

* server: move idle slot KV clearing to slot release

The save "cost" is now paid by the finishing request.

* server: add --kv-clear-idle flag, enable by default

* server: skip clearing last idle slot, clear on launch

* server: test --no-kv-clear-idle flag

* server: simplify on-release clearing loop

* server: remove on-release KV clearing, keep launch-only

* cont : clean-up

* tests: update log strings after --clear-idle rename

* tests: use debug tags instead of log message matching

* test: fix Windows CI by dropping temp log file unlink

---------

Co-authored-by: Georgi Gerganov <ggerganov@gmail.com>
gagallo7 added a commit to makaveli10/qvac-ext-lib-llama.cpp that referenced this pull request Jul 30, 2026
gagallo7 added a commit to gagallo7/qvac-fabric-llm.cpp that referenced this pull request Aug 10, 2026
The upstream target of this former fixup! (50e0ad0, --clear-idle
ggml-org#20993) already landed upstream, so this stays a standalone commit.

Relying on exact log text is brittle, especially across rebases with
upstream changes; use the timings fields instead and drain remaining
logs for test cleanliness.
zommiommy pushed a commit to zommiommy/llama.cpp that referenced this pull request Aug 18, 2026
…org#20993)

* server: clear idle slots KV from VRAM (LLAMA_KV_KEEP_ONLY_ACTIVE)

* server: move idle slot KV clearing to slot release

The save "cost" is now paid by the finishing request.

* server: add --kv-clear-idle flag, enable by default

* server: skip clearing last idle slot, clear on launch

* server: test --no-kv-clear-idle flag

* server: simplify on-release clearing loop

* server: remove on-release KV clearing, keep launch-only

* cont : clean-up

* tests: update log strings after --clear-idle rename

* tests: use debug tags instead of log message matching

* test: fix Windows CI by dropping temp log file unlink

---------

Co-authored-by: Georgi Gerganov <ggerganov@gmail.com>
gagallo7 added a commit to gagallo7/qvac-fabric-llm.cpp that referenced this pull request Aug 21, 2026
The upstream target of this former fixup! (50e0ad0, --clear-idle
ggml-org#20993) already landed upstream, so this stays a standalone commit.

Relying on exact log text is brittle, especially across rebases with
upstream changes; use the timings fields instead and drain remaining
logs for test cleanliness.
korcan-h pushed a commit to korcan-h/qvac-fabric-llm.cpp that referenced this pull request Sep 1, 2026
The upstream target of this former fixup! (50e0ad0, --clear-idle
ggml-org#20993) already landed upstream, so this stays a standalone commit.

Relying on exact log text is brittle, especially across rebases with
upstream changes; use the timings fields instead and drain remaining
logs for test cleanliness.

(cherry picked from commit 1df9cb1)
gianni-cor added a commit to tetherto/qvac-fabric-llm.cpp that referenced this pull request Sep 4, 2026
* fix: QVAC-21320 tiled NORM dispatch — never exceed maxComputeWorkGroupCount

Review fix (PR #174): the fused-norm change dispatched GGML_OP_NORM as a direct {ne01, ne02, ne03} grid; on large row counts ne01 can exceed maxComputeWorkGroupCount[0] (spec minimum 65535) and trip the GGML_ASSERT in ggml_vk_dispatch_pipeline, where the previous flattened/tiled dispatch handled arbitrary ggml_nrows.

Restore the flattened {512, 512, N} row tiling on the host (same group as SOFT_MAX/SUM_ROWS) and reconstruct {row, channel, sample} in norm.comp from the flat workgroup id (formula shared with soft_max.comp), with a workgroup-uniform bounds return for the tiling round-up. dst offset is unchanged: flat_row == (samp*nchannels + channel)*nrows + row by construction. No behavioural change for in-range shapes; the fusion's dispatch-count reduction is untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit 68e6c529ccade61db2f4b0429f0d729a25e49058)

* fix: QVAC-21914 downgrade non-coopmat clip FA hard-disable to AUTO (budget-aware)

The warmup-time hard-disable for GPU projectors without efficient
(coopmat) flash attention replaced AUTO/ENABLED with DISABLED, which
short-circuited the budget-aware AUTO heuristic in
clip_resolve_flash_attn_type(). At high n_pos (image_tile_mode=disabled
with image_max_tokens=4096 -> 16384 ViT patches) the forced explicit
attention path materializes an O(n^2 * n_head) score matrix, growing RSS
to ~12 GB and getting the process lmkd-killed on Pixel 9 Pro
(runQwen35ImageTileModeTokensTest).

Downgrade to AUTO instead and record the inefficiency in
clip_ctx::fa_backend_inefficient, which now also enables the AUTO cutoff
default (previously Mali-detection only) so any non-coopmat backend gets
the per-image budget decision: explicit attention below the cutoff (fast
on scalar-FA GPUs), memory-frugal scalar FA at/above it or when the
explicit scratch would not fit device memory. Explicit user DISABLED is
still honored, and MTMD_CLIP_AUTO_FA_MIN_KV still overrides the cutoff.

(cherry picked from commit cceee2288c686df79634bcb029de629542e45756)

* fix: QVAC-21914 bound ggml-opencl submissions (periodic clFlush + FA q-chunking)

On Galaxy S25 Ultra (Adreno 830, OpenCL) the monolithic 16384-patch ViT
encode (image_tile_mode=disabled, image_max_tokens=4096) faults the GPU
near the end of the encode (Adreno-GSL log_gpu_snapshot fires before any
decode work reaches the device), after which the driver aborts the
process from cl_a8x_cmdbuf_mgr_submit_ibs (os_exit) on the next
submission. Two unbounded behaviours plausibly drive the fault and both
are bounded here:

- ggml_backend_opencl_graph_compute enqueued entire graphs (thousands of
  nodes, ~48 s of GPU work for the failing encode) with no intra-graph
  flush. Now clFlush every GGML_OPENCL_FLUSH_INTERVAL nodes (default 64,
  0 disables) so the GSL command-buffer manager receives bounded
  batches. clFlush submits without stalling the host.

- ggml_cl_flash_attn issued one dispatch covering all q rows; at
  n_q = n_kv = 16384 every workgroup loops the full KV, making a single
  very long kernel. Now chunked along q rows at GGML_OPENCL_FA_MAX_NQ
  rows per dispatch (default 4096, 0 disables) with a clFlush between
  chunks. The split is exact: the kernel resolves its q row relative to
  the Q/O/mask base offsets, is_causal is always 0 (masking is explicit)
  and alibi/sinks depend only on the head index, so shifting the row
  base via byte offsets while shrinking n_q is mathematically identical.
  No .cl kernel changes.

The 512-token image-chunk decode is not implicated: the S25 VLM
benchmark ran 304 full-512-row ubatch decodes cleanly. Only the giant
monolithic encode (5-8x beyond anything previously run on this backend)
triggers the fault.

(cherry picked from commit 82a26df6e5ce24053101a148c0423d54515226bd)

* fix: QVAC-21914 review fixes — work-budget flush, memory-clamp rework, tests

Addresses the pre-merge review findings on the two QVAC-21914 crash-fix
commits (P1/P2 performance, C1/C2 correctness, S1/S2 robustness, K nits):

- ggml-opencl: gate the periodic graph flush on accumulated estimated WORK
  (GGML_OPENCL_FLUSH_WORK_MB, default 512 MB) instead of a bare node
  counter. Per-token LLM decode graphs never reach the budget by
  construction, so the decode hot path stays submission-free; the
  16k-patch encode still flushes dozens of times. Single touch point in
  graph_compute (no more per-fusion-branch duplication).
- ggml-opencl: both tunables move onto ggml_backend_opencl_context,
  resolved once at init with strtol-based parsing (clamp, warn on garbage
  instead of silently disabling the mitigation) and GGML_LOG_INFO'd like
  the file's other env knobs. FA chunking reads the context field.
- ggml-opencl: GGML_ASSERT(is_causal == 0) before the FA chunk loop — the
  kernel's causal-boundary formula needs the TOTAL n_q, so chunks after
  the first would silently corrupt output if causal FA were ever enabled
  here; keep the invariant loud. Explicit n_q == 0 guard.
- clip: rework the AUTO cutoff memory clamp. Total memory now provides the
  STABLE fast-path clamp (explicit scratch <= total/4); free memory (a
  volatile, load-dependent number) may only lower the cutoff further via
  the hard-fit requirement (scratch <= free), never the old free/2
  heuristic that silently pushed normal-size Mali images onto the ~2.6x
  slower scalar-FA path under momentary memory pressure. No memory info at
  all now fails SAFE at a conservative 2048-patch cap instead of trusting
  the raw 4096 default (~3.2 GB scratch at n_head=16). The arithmetic is
  extracted into clip_fa_effective_min_kv() (pure, exposed via clip.h for
  tests).
- tests: test-clip-fa-cutoff (pure CPU, locks in the fast path, the P2
  regression guard, the fail-safe cap and edge cases; passing) and
  test-opencl-fa-chunking (chunked-vs-CPU numerical parity over unchunked
  / exact-chunk / partial-last-chunk / n_q==1, masked and unmasked, with
  GGML_OPENCL_FA_MAX_NQ=64 and a 1 MB flush budget; self-skips without a
  capable OpenCL device — PoCL lacks FP16, so it executes on Adreno-class
  hardware).
- clip warmup comment: note ggml-opencl also lands in the "no efficient-FA
  query" bucket and its giant-encode fault is handled by the submission
  bounding inside that backend.

GGML_OPENCL_FLUSH_INTERVAL (node-count knob) is replaced by
GGML_OPENCL_FLUSH_WORK_MB; GGML_OPENCL_FA_MAX_NQ semantics unchanged.

(cherry picked from commit fc09f36b4435a23b1e261e20ca9d6122d68c2404)

---

b10297 rebase:

- Squash a68b35970: test-opencl-fa-chunking: call
  ggml_backend_load_all() and select the device by backend registry
  name ("OpenCL") instead of substring-matching the device name.

Squashed-with: 649de77eb, a68b35970

* fix: QVAC-21914 make clip_fa_effective_min_kv inline (Windows DLL link)

The pure AUTO-budget helper was defined out-of-line in clip.cpp and
declared in the internal clip.h. On Windows mtmd builds as a shared
library exporting only the MTMD_API-decorated public API; the internal
clip_* symbols are absent from mtmd.lib, so test-clip-fa-cutoff (the
first cross-DLL-boundary consumer of a clip_* symbol) failed to link
(LNK2019). Linux/macOS export all default-visibility symbols, so it
linked there.

Move the function inline into clip.h (with its NO_MEMINFO_CAP constant);
the test and clip.cpp both compile their own copy — no DLL export of an
internal helper. CLIP_AUTO_FA_MIN_KV_MALI_DEFAULT stays in clip.cpp (its
only user). Verified: mtmd + test-clip-fa-cutoff build and the test
passes.

(cherry picked from commit 99d6042207cb255a8334d97546957fbab19bfc66)

* fix: QVAC-21914 address PR review — warn on env clamp, cover n_head guard

- parse_env_i64: GGML_LOG_WARN when an in-range-but-too-large
  GGML_OPENCL_FLUSH_WORK_MB / GGML_OPENCL_FA_MAX_NQ is clamped to max,
  matching the file's convention of logging every overridden value
  (previously the clamp was silent).
- test-clip-fa-cutoff: the n_head=0 case passed total_mem==free_mem==0,
  which short-circuits to the NO_MEMINFO cap before any sqrt(.../n_head)
  branch runs — the div-by-zero guard was never exercised. Pass 16 GB
  total so the total-memory clamp runs with n_head=0; without the guard
  the (int)sqrt(x/0) path would now fail the assertion.

Both from yingying0906's review; Windows/CPU-only surface, no Android
behavior change.

(cherry picked from commit 3da3e05fc459778f343da6ce0b796766544546a7)

* fix: QVAC-21914 flush after enqueuing the budget-crossing node

The graph_compute work-budget flush ran BEFORE the current node was
dispatched: it accounted the node's work, and on crossing the budget
flushed (submitting only the prior batch) then reset the counter to 0 —
so the crossing node started a fresh batch. A large op, or the last
large segment of the graph, could therefore begin an unflushed batch and
be submitted unbounded at the implicit end-of-graph finish, defeating
the bound.

Move the budget check below the dispatch (convert the fused-op
continue chain to if/else so every path reaches one touch point), so the
node that crosses the budget is part of the flushed batch. Reported by
@gianni-cor.

(cherry picked from commit 7056f4cadb60aa255a333314a45c6d520ef88637)

* chore: QVAC-21914 address PR review — flush-cadence wording, nits

Non-behavioral cleanups from the PR #181 review pass:

- ggml-opencl graph_compute: correct the flush-cadence comment. The old
  "per-token decode hot path submission-free by construction" claim was
  false for multi-GB models — a decode step streams the whole model, so
  its estimated work crosses the default 512 MB budget a few times per
  token. Reworded to state that accurately (cost is negligible in
  practice since clFlush is non-blocking, and it is tunable/zeroable to
  make decode fully submission-free). Mechanism unchanged.
- ggml_cl_flash_attn: GGML_ASSERT(q->ne[1] <= INT32_MAX) before the int
  n_q truncation, since the q-chunk loop accumulates into an int and
  derives cl_ulong offsets from it (defensive; not reachable with real
  shapes).
- Consistency: normalize the ticket tag to bare `QVAC-21914` (drop the
  `qvac ` prefix) in clip.h and tests/CMakeLists.txt, matching the .cpp
  files and the fork's QVAC-21257 precedent.
- tests/CMakeLists.txt: move the unconditional test-opencl-fa-chunking
  registration up beside test-copy-tbq-subgroups (its self-skipping
  sibling) instead of sitting right after the LLAMA_MTMD endif() where it
  read as MTMD-gated; add a comment noting it deliberately does not link
  mtmd.

No functional change to the fix; local mtmd + both tests build,
test-clip-fa-cutoff passes.

(cherry picked from commit 2b927cf3910c07c8ccebae984fc8eabc2ffa17b0)

* vulkan: restore the nb00 element stride in the fused norm shader so non-contiguous (permuted) inputs read the right columns

Signed-off-by: Marcus Edel <marcus.edel@collabora.com>
(cherry picked from commit 6053e42d473e0a828682fd282b11a8a5cc198603)

* cuda: Fix OUT_PROD op support claim

Only claim OUT_PROD support for src types ggml_get_to_fp32_cuda can
requantize.

ggml_cuda_out_prod converts non-F32 srcs to F32 before the f32-only
cuBLAS GEMM and aborts on e.g. TQ2_0 which has no CUDA dequantizer.

(cherry picked from commit d52948582510fc8ed168998473f901698485e76d)

* ggml-opencl: build flash-attn kernels without finite-math

The OpenCL kernels are compiled with -cl-finite-math-only and
-cl-fast-relaxed-math, which let the compiler assume no Inf/NaN. The
flash-attention online softmax initialises its running max to -INFINITY
and masks padded scores with -INFINITY, so finite-math miscompiles the
init/masking path.

Compile the flash-attention programs with a relaxed option set that
drops -cl-fast-relaxed-math, -cl-finite-math-only and
-cl-unsafe-math-optimizations (keeping -cl-mad-enable for speed) so the
-inf sentinels behave correctly.

Also harden the strip: erase every occurrence of each flag (not just the
first) and GGML_ASSERT that no finite-math/fast-math/unsafe-math flag
survived, so a future compile_opts spelling/spacing change fails loudly at
load time instead of silently reintroducing the -INFINITY miscompile.

Re-ported onto b9840's rewritten OpenCL flash-attn (upstream PR #14987 +
follow-ups): the original per-dim kernel-compile loop is gone, so the strip
is applied once in ggml_opencl_fa_compile_opts(), the single site every FA
variant (F16/F32/F32_F16/Q8_0/Q4_0/PRE and _SPLIT) is compiled through.
Squashed re-port of 0cbe36259 + c1dace72b (finite-math part).

(cherry picked from commit 348a910361bdd8b4bd0f8e70c8e793ef7fbc5ee5)

* ggml-opencl: treat null attention mask as bidirectional, not causal

The flash-attention dispatch inferred causal masking from shape with
`is_causal = (mask == NULL && n_q > 1 && n_q == n_kv)`. A null mask means
no masking, i.e. bidirectional attention (the SigLIP vision and embedding
encoders), while causal attention always supplies an explicit causal mask
in this codebase (llama-graph.cpp build_attn passes a kq_mask filled with
-INFINITY). The heuristic therefore wrongly made the bidirectional
Qwen3-VL vision tower attend causally, so each patch only saw earlier
patches and the image embedding was corrupted.

Set is_causal = 0 unconditionally; causality is always expressed via the
explicit mask. This cannot regress the LLM, which already passes a real
causal mask (is_causal was already 0 for it) and relies on that mask.

Document the invariant in ggml_cl_flash_attn: a null mask is treated as
bidirectional, so any caller needing causal masking must supply an explicit
causal mask rather than relying on shape inference.

Re-ported onto b9840's rewritten OpenCL flash-attn; b9840's own q-chunking
path already GGML_ASSERTs is_causal == 0, so this is consistent with the
existing code. Squashed re-port of 51dbb1756 + c1dace72b (is_causal part).

(cherry picked from commit 7ae4bc939f21f3b5c72ab3caa01c806605d6a15b)

* ggml-opencl: add trailing barrier in f32/f16 flash-attn tile loop + guard upscale zero dims

The f32/f16 flash-attention kernels load K/V tiles into local memory,
barrier, read them, then loop to overwrite the tiles for the next K/V block
without a trailing barrier. Out-of-range lanes (the last partial BLOCK_M
block) `continue` past the read and race ahead into the next tile load while
active lanes are still reading l_k/l_v. With n_kv > BLOCK_N (e.g. the
bidirectional vision tower, n_kv=247) this corrupts the shared tiles.

Add a trailing barrier(CLK_LOCAL_MEM_FENCE) at the end of the K/V block loop
and guard the score computation with `if (my_query_row < n_q)` instead of an
early continue. flash_attn_f32_f16.cl already uses that guard + trailing
barrier after b9840's redesign, so it is left unchanged.

Also guard zero source dimensions in ggml_cl_upscale: the sf* scale factors
divide by the source dims, so a zero source dim yields +inf; the existing
early-exit only covered zero destination dims.

Re-ported onto b9840's rewritten OpenCL flash-attn (b9840 widened the score
unroll to j += 4; only the divergence guard + trailing barrier are re-applied,
the body is unchanged). Squashed re-port of dc64397d2 + b7ad6d4e2.

The barrier fix is a GPU-scheduling race whose only proof is on-device; the
original b7ad6d4e2 validated on S25 Ultra / Adreno 830 (Qwen3-VL GPU vision
projector matches CPU exactly, Delta 0.0 pp, ~26% faster on encode). Re-verify
on-device before merge.

(cherry picked from commit ce54dd55ab6dd590ee597c868b12071b11d01732)

---

b10297 rebase:

- Squash 97a1ecd12: flash_attn_f32.cl: apply the divergence guard
  unconditionally and drop the FA_SG<64-only trailing barrier; the
  tile race reproduces even on a single 64-wide Adreno subgroup
  (Adreno 830).

Squashed-with: 278014e04, 97a1ecd12

* server tests: detect cache restore via timings, not exact log text

The upstream target of this former fixup! (50e0ad08f, --clear-idle
#20993) already landed upstream, so this stays a standalone commit.

Relying on exact log text is brittle, especially across rebases with
upstream changes; use the timings fields instead and drain remaining
logs for test cleanliness.

* ci: install jinja2 explicitly in the venv so test-jinja-py doesn't depend on the torch pin surviving pip install

Signed-off-by: Marcus Edel <marcus.edel@collabora.com>
(cherry picked from commit 2d3f4034bd6dcf84089c2874f1d1ab26f654576f)

* ci: add backend op-coverage manifest guard + SVE variant tripwire

A supports_op() regression never fails test-backend-ops: the case falls
back to CPU and is reported 'not supported [backend]', i.e. skipped.
e09ae0b71 removed Q4_1/Q4_K from OpenCL MUL_MAT supports_op with zero
test failures

---

b10297 rebase:

- Squash e8fb282d9: add the UPSCALE f32 bilinear|antialias row to the
  opencl-pocl op-coverage manifest.

Squashed-with: 5587e68af, e8fb282d9

* tests: add no-mask n_q == n_kv FLASH_ATTN_EXT cases (vision-tower shape)

The FA sweep uses kv in {113, 512, 1024} x nb in {1, 3, 32, 75}, so nb
never equals kv and the mask==NULL && n_q==n_kv shape -- exactly what a
ViT self-attention layer produces -- is never exercised. A backend that
infers causality from that shape (OpenCL's is_causal heuristic,
ggml-opencl.cpp:15131) silently computes causal attention for the whole
vision tower and no CI test goes red.

Add explicit bidirectional cases at n_q == n_kv == 247 and 256 for head
sizes 64 and 80 (both in the OpenCL FA supported-dims table). 247 (odd)
additionally leaves partial tiles for any power-of-two tile size; 256 is
the aligned control separating causality bugs from tiling bugs.

Expected red on OpenCL until the is_causal heuristic is removed
(re-port of b9840 7ae4bc939); green on CPU/Vulkan/CUDA/Metal.

Note for landing: order this commit after the is_causal fix so the
series stays bisectable-green on OpenCL hardware.

Assisted-by: Claude (Anthropic AI)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DbaV79zWiPZJq1LrdWGDTj
(cherry picked from commit a0fe46a41d5c8e02f9923049c0a16cbaef8d7439)

* tests: add partial-tile FLASH_ATTN_EXT cases across KV-type kernel variants

n_q=33 with n_kv=513 leaves out-of-range query lanes for any pow2 query
tile (Adreno OpenCL uses BLOCK_M=64 for dk 64/128) and a 1-valid-row
final KV tile (513 = 16*32 + 1), with 17 tile-loop iterations worth of
barrier crossings. Tiled kernels must keep out-of-range lanes inside the
tile loop for the barriers while excluding them from the score loop; an
early continue past the tile barrier (the b9840 ce54dd55a race class) or
a missing trailing barrier corrupts the shared K/V tiles for in-range
lanes.

Cover each KV type separately: backends that specialize kernels per KV
type (OpenCL picks flash_attn_f32.cl for f32 KV, f32_f16(+split) for f16
KV since n_kv=513 crosses the split threshold, and the q8_0/q4_0 tiled
kernels for quant KV) would otherwise leave those variants untested at
this shape. Note flash_attn_f16.cl itself is only reachable with an f16
Q tensor, which test-backend-ops never generates (Q is always f32) -- it
stays covered only by code review.

The race is scheduling-dependent: in-order devices (pocl) and drivers
with cooperative-matrix FA paths will likely pass even with the bug;
these shapes make the sweep able to catch it on the affected hardware
class (original repro: Adreno 830).

Validated: CPU supports all 5 cases; Vulkan RADV 5/5 PASS on 7900 XTX
and Raphael iGPU.

Assisted-by: Claude (Anthropic AI)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DbaV79zWiPZJq1LrdWGDTj
(cherry picked from commit ab6ecf6b4ea23533640988f0557b11c48106bf01)

* vulkan: add GFX1151 q8_0 BK64 matmul path

Add the tuned cooperative-matrix path without experimental runtime controls.

(cherry picked from commit 9675568aeb84d91aa0653bcf93b0e6aa225eaa7d)
Co-authored-by: Guilherme Gallo <guilherme.gallo@collabora.com>

* cpu: optimize DeepSeek4 HC post

Vectorize the fused HC post path for ARM and x86 while preserving scalar fallback behavior.

(cherry picked from commit 7c21b4a5400ac9c99576dbce401bf26b2209175f)

* vulkan: add Lightning Indexer

Assisted-by: GPT-5.6 Sol
(cherry picked from commit 5f41c2e29ee5b5cdcee383fef2208173604c7704)
Build-fix-hoisted-from: d19b9ddbadc9 (drop stray closing brace after ggml_vk_lightning_indexer)

* vulkan: add DeepSeek4 HC comb

Assisted-by: GPT-5.6 Sol
(cherry picked from commit 9afa822b70fa99a08d14f9c3369ab1a674332a55)

* vulkan: add DeepSeek4 HC pre

Assisted-by: GPT-5.6 Sol
(cherry picked from commit e5d7104869fe4a9d4d1aa6d1b5d0e6a5bb963bd0)

* vulkan: add DeepSeek4 HC post

Assisted-by: GPT-5.6 Sol
(cherry picked from commit 16d647b7046d7897eaee7c6f9d4ceba20dcd3f17)

* tests: cover DeepSeek4 fused operations

Add fused-versus-fallback correctness and benchmark cases for HC post and the Lightning Indexer.

Assisted-by: GPT-5.6 Sol
(cherry picked from commit b5dcb61adceb221ec3c688d6c957966a7bf4f5c0)

---

b10297 rebase:

- skip_backend for DSV4_HC_POST_BIT_EXACT matched only the "CUDA" reg
  name, but HIP builds register the same backend as "ROCm"
  (GGML_CUDA_NAME), so the FMA-contraction skip never fired there and
  the bit-exact gate failed deterministically on ROCm (6 cases,
  ERR=1.0).

* vulkan: add typed K cache support to Lightning Indexer

Generate per-K-type generic pipelines and 32/64-head CM1 and CM2 variants, with quant-aware stride handling and guarded pipeline selection.

CM1 stages decoded FP16 tiles under shared-memory limits; CM2 uses FP16 decode callbacks for quantized K tiles.

Assisted-by: GPT-5.6 Sol
(cherry picked from commit d2414ec43e06309824c332c5e00bbb90c0bec5be)

* tests: cover typed Lightning Indexer K caches

Exercise matrix paths for 32- and 64-head layouts across supported K-cache types, including dispatch-tail boundaries and a strided-Q scalar fallback.

(cherry picked from commit 1a8d1a67ebedd5cdd71ea874d339400da50d0449)

* vulkan: support quantized concat

Keep quantized cache concatenation on the GPU to avoid per-layer CPU fallbacks and excessive graph splits.

(cherry picked from commit 7e530286b4d36b2c70ceec445e380bcdbd9459f8)

* metal: support quantized concat

(cherry picked from commit 581bbdafb9e4a101970787107cb9baceb3c2823b)

* metal: support strided f16 add

Keep DeepSeek V4 mask construction on the GPU with a vectorized path for strided inputs.

(cherry picked from commit 807cc6aa0bf6acfaf741be41494643632a059dd7)

* meta: handle Lightning Indexer split state

Treat the fused indexer conservatively like the other DeepSeek4 fused operations instead of aborting in meta backends.

(cherry picked from commit 7aad3a2ef844fce37c729d3329f159105ad52712)

* ci: extend macOS ARM test timeout

Allow the expanded backend operation suite to complete on slower Apple runners.

(cherry picked from commit edbc216ba6cf7b7e2bc482fd07377f6aa2a54e1e)

* ci: make nproc portable and verify it in self-hosted deps

ci/run.sh uses nproc for build -j and quantize thread counts, but macOS
runners often lack GNU coreutils, so $(nproc) silently expanded to
nothing, polluting the logs

Define a sysctl-backed shim when nproc is missing, fail setup outright
when neither is available, and surface the gap in
gg_check_build_requirements

On the Graviton jobs, install coreutils explicitly and assert nproc
resolves at the end of the Dependencies step so a missing tool fails
there instead of mid-run

* ci: fail fast when jinja2 is missing from the CI venv

Assert jinja2 is importable by the same python3 the test spawns and
abort setup with a clear error instead.

* Add CHANGELOG.md

Signed-off-by: makaveli10 <vineet.suryan@collabora.com>

* ggml-opt: abort training when the backend graph compute fails instead of accumulating garbage results

Signed-off-by: Marcus Edel <marcus.edel@collabora.com>

* ggml-metal: default n_cb back to 1 on iOS to fix the A18 GPU hang when resuming finetuning from a pause checkpoint

Signed-off-by: Marcus Edel <marcus.edel@collabora.com>

* ggml-metal: only track memory ranges when the encoder is concurrent, fixing the A18 GPU hang from barriers encoded into serial encoders

Signed-off-by: Marcus Edel <marcus.edel@collabora.com>

* ggml-metal: avoid per-iteration whole-block copies in the quantized OUT_PROD kernels so finetuning backward passes stay under the iOS GPU watchdog

Signed-off-by: Marcus Edel <marcus.edel@collabora.com>

* vulkan: drain glslc stdout/stderr concurrently

This makes shader compile fail fast.

execute_command() drained the child stdout pipe to EOF before touching
stderr. Poll both pipes and read whichever is ready until both hit EOF

A glslc invocation that emits more than a pipe buffer of diagnostics
(e.g. hundreds of errors from a broken shader variant) blocks in
write(2) on the full stderr pipe, the parent blocks in read(2) on
stdout, and the whole shader-gen step deadlocks instead of failing the
build

Assisted-by: Claude Fable 5 <noreply@anthropic.com>

* QVAC-21550 infra: roll out canonical security baseline (TruffleHog + CodeQL) to qvac-fabric-llm.cpp (#177)

* QVAC-21550 infra: add canonical security baseline caller (TruffleHog + CodeQL)

* QVAC-21550 fix: drop paths-exclude (unsupported by reusable security v0)

* QVAC-21550 fix: drop secrets: inherit (baseline needs no repo secrets; github.token suffices)

* QVAC-21550 fix: repin security baseline to qvac-actions 0.2.0 (buildless c-cpp)

(cherry picked from commit 4b81205eecc25e6ae43f1b88b317da646d7606a6)

* QVAC-22747 infra: add weekly schedule to CodeScan caller (#189)

The canonical CodeScan baseline currently runs on push/PR only, so the
QVAC-19056 commitment to a weekly scan is unmet. Add a scheduled cron
(staggered per repo across the fleet) alongside the existing
push / pull_request / workflow_dispatch triggers. No other change.

(cherry picked from commit 5922019a656e1ad84e27b72f237ae49a354c6615)

* QVAC-22740 infra: bump CodeScan caller to qvac-actions 0.3.0 (#195)

Bump the reusable security workflow pin 0.2.0 -> 0.3.0 (SHA bbb0740e). 0.3.0
adds the findings-export artifact (export-report default on), so each run now
publishes a downloadable security-scan-report (findings.json/md + SARIF).

(cherry picked from commit de769869f0e29d4e002c28e8e796ab6cd8e0593f)

* chore(ci): remove qvac-collabora-merge from CODEOWNERS

Per QIP tier-1-approval-change, only management and team-lead teams
should be listed as code owners.

(cherry picked from commit 7b02a3c3c13c271dbf1b1a1059c568ac6cb1b96c)

* squash! metal: port OUT_PROD, SILU_BACK, SOFT_MAX_BACK, RMS_NORM_BACK ops to split architecture

Drop the downstream SILU_BACK port: upstream b10297 ships its own typed kernel_silu_back_<type> pipeline, op encoder, and supports_op case, so the downstream _4-suffix variant became a duplicate definition (redefinition errors in ggml-metal-device.cpp/ggml-metal-ops.cpp and a duplicate case label in ggml-metal-device.m supports_op).

Assisted-by: Claude Fable 5

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CBhFiEgFVPcnDBpdP4e6Yk

* tests: relax dsv4 bit-exact check to ulp-based tolerance

Signed-off-by: Marcus Edel <marcus.edel@collabora.com>

* squash! hip: allow-list rwkv_wkv_f32<128> VGPRs

Refresh the mul_mat_q<Q2_K, 64, true> mangled name

Upstream 1a064ab09 (NVFP4 W4A4 activation quantization) added a const
float * y_scale kernel argument, which changed the mangled signature and
orphaned the previous allow-list entry

* ggml-webgpu: fix CI errors from #25025 and #25262 (#26566)

* test new flash_attn test

* rebase and fix to disable subgrou matrices when max_kv_tile == 0

* delete log output

* Add i32 support to cpy and enables the all ops test

* restore the non target ci tests

* comment out of TODO of build-cpu.yml

* fix format

* tests: add more cases and perf mode support for backward GDN

* ggml-metal: add getter function for threadExecutionWidth

* ggml-metal: allow ggml_metal_library_get_pipeline_* fns to derive thread dispatch sizes

* ggml-metal: optimizes gated-delta-net back op

Replicates the same optimizations applied to the
vulkan version of the GDN back kernel

* ggml-cuda: adds support for backwards gated delta-net

* ggml-cuda: adds sigmoid-back

* ggml-cuda: implement geglu backward

Signed-off-by: makaveli10 <vineet.suryan@collabora.com>

* ggml-cuda: implement ssm-conv-back-sx

* ggml-cuda: implement ssm-conv-back-c

* QVAC-23075 feat: add VisionPsy Nano and its Flash preprocessing rule

VisionPsy Nano is a siglip encoder with an idefics3-style pixel-shuffle merge and a
single mm_fc projector, so route it through clip_graph_siglip and reuse the llava-uhd
preprocessing. Four differences from idefics3, all taken from get_image_string() in
the reference processors.py: position embeddings are interpolated to the actual patch
grid, the overview image is emitted first, the delimiters are <|global_image|> and
<row_%d_col_%d> with no fake_token_around_image and no row-end token, and each image
carries an <image: N> ordinal label once a prompt holds more than one. That label is
not in the vocab, so it goes through BPE as text.

When the slice grid is 1x1 the overview and the single slice are the same crop, so
only one of them is sent, and it is the slice. GlobalAndSplitImages.forward returns
the split patch untouched for that grid, before it resizes a global patch, and the two
paths do not share a resize kernel: the slice is rendered with image_resize_algo_rf
(bicubic) and the overview with image_resize_algo_ov (bilinear).

The published mmproj GGUFs declare clip.projector_type = "custom", so add a read-only
alias table mapping that string onto PROJECTOR_TYPE_VISIONPSY. The alias is gated on
general.name as well, so another model shipping "custom" is not silently loaded as
VisionPsy. PROJECTOR_TYPE_NAMES stays canonical and "visionpsy" is what we write out.

lookup_token() returns LLAMA_TOKEN_NULL on a miss and no caller checks it, so a vocab
without <|global_image|> would splice a garbage id into the prompt. It now throws, but
only when there is a vocab to look in: mtmd_get_memory_usage builds a context with no
text model and every token misses there, which otherwise cost every llama-server start
with this mmproj its mmproj VRAM reservation.

The base and Flash checkpoints differ in exactly one thing, resize_to_max_side_len.
Base always stretches the long side to max_img_size; Flash rounds it up to a whole
number of slices and only then caps it, so an image below the cap keeps its own
resolution. Their mmprojs are byte-different but declare identical vision hparams, so
nothing in the GGUF distinguishes them and Flash was silently running base
preprocessing, inflating a 256x256 image into a 4x4 grid of upscaled slices.

Add clip.vision.preproc_no_upscale, read from the GGUF when present, overridable
through mtmd_context_params.image_no_upscale and the new --image-no-upscale flag. The
published mmprojs do not carry the key, so today the flag is what selects the variant;
a re-converted Flash mmproj would need no flag. Tri-state, -1 keeps the model default,
so every existing caller is unaffected.

The short side is computed in double. The reference is math.ceil(short * scale / p) on
Python floats, and where the true quotient lands on an integer, float32 and exact
integer arithmetic each disagree with it by a whole slice row: over every size pair up
to 3000x3000, float32 differs in 171 cases and exact integers in 92, double in none.
image_size == 0 is legal in general but calc_size_no_upscale divides by it, so it is
rejected at load, where the rest of the loader reports bad config, rather than
asserting per image.

Flash on 1024x768 goes from 13 slices to 5, encode 361 ms to 139 ms, prefill 858
tokens to 338; on 256x256 from 17 slices to 1, 474 ms to 38 ms, 1118 tokens to 78.
DocVQA ANLS over 30 examples is unchanged at 0.825 versus 0.824, as expected, since
those scans are larger than the cap and the two rules converge above it.

Both checkpoints are registered in the vision test list. They use the published
GGUFs, which -hf resolves to both the language model and the mmproj, and answer "the
new york times" to the harness default prompt, so they pass the existing
new-york / men-walk assertion. Flash needs the flag on its row, since its published
mmproj carries no clip.vision.preproc_no_upscale key and would otherwise duplicate
the base row's preprocessing.

(cherry picked from commit fc07c46a16298db9514ba66dddb3573eb74d9829)

* QVAC-23075 fix: ask the Adreno transpose predicate about the parent, not the view

enable_adreno_trans_weight gates three things as a unit: the transpose in set_tensor,
the restore in get_tensor and the adreno gemm/gemv selection in mul_mat. set_tensor
returns early for views and always decides on the parent, but get_tensor asked about
the view, so the two could disagree on the shape predicate and pair a transposed
writer with a non-transposed reader.

Unrelated to VisionPsy, and kept separate for that reason. The shape predicate itself
already rejects the q8_0 layouts the transpose asserts on, so this only closes the
view-versus-parent gap.

(cherry picked from commit 7a6425a3add96f2598110a5953786866c9d49e0c)

* QVAC-23075 fix: reconstruct the q8_0 parent before reading a view back on Adreno

The SoA buffers cover the whole parent and the Adreno transposed layout is indexed by the
parent's row count, but get_tensor sized its staging buffer and its dispatch from the view.
A view with fewer rows than its parent therefore strided the transposed source wrongly, and
once the dispatch rounded the row count up to the 64-wide work group it wrote past the end
of the buffer: a 4096x1 view of a 4096x4096 parent allocates about 4.3 KB and the kernel
restores 64 rows into it, about 278.5 KB. Reconstruct the parent instead, exactly as
set_tensor already does, and read the view out of it at view_offs.

The kernel also gets the row guard the rounded-up dispatch needs, which closes the same
overflow for any parent whose row count is not a multiple of 64.

(cherry picked from commit 62162f7fa0bdaf5849bc19f6d62418c8c94205be)

* QVAC-23075 fix: size the aspect-preserving refine in double, not float

calc_size_preserved_ratio scaled in float32 while both reference processors do it in Python
floats. Where the true product lands exactly on a multiple of align_size, float32 rounding
pushes it just past and the ceil buys a whole extra row or column of slices. 960x720 with
image_size 512 and longest_edge 2048 is the common 4:3 case: the reference refines to
2048x1536 and slices 4x3, float32 gave 2048x2048 and sliced 4x4, so the image gained 4
slices and 256 image tokens the model was never trained to see.

Verified against the local Metal build on the base checkpoint: 960x720 drops from 17 image
encodes to 13, that is a 4x4 grid to a 4x3 grid plus the overview. 1024x768, 640x480 and
256x256 are unchanged, their scale factors are exact in float32. Sweeping every 16-pixel
size pair up to 3000x3000, float32 and double disagree on 10 of 33856, all of them 4:3 or
3:4. The idefics3 and pixtral paths share this helper and both references are also double,
so they move the same way.

(cherry picked from commit a151b019c2bf90393a58bb1162d4ae2a54bceb37)

* QVAC-23075 fix: reject a no-upscale cap below one slice

calc_size_no_upscale() clamps both target sides into [image_size, image_longest_edge], and
std::clamp requires lo <= hi, so GGUF metadata with a cap smaller than the slice size is
undefined behaviour rather than merely a bad size. Validation already demanded both be
positive; demand the ordering too, next to it and after the flag override, so it covers the
CLI flag as well as the GGUF key.

(cherry picked from commit 0b61b39c5698b69419a7f535c518a5c4e2eb927c)

* QVAC-23075 fix: keep the NUL terminator out of string_format's result

clip-impl.h's string_format returned std::string(buf.data(), buf.size()) over a buffer sized
size + 1, so the terminating NUL sat inside the string and length() was one too long.
mtmd.cpp includes clip-impl.h and not common.h, so that is the overload the `<image: N>`
ordinal label is built with, and mtmd_tokenize_text_internal tokenizes text.data() with
text.length(), all size + 1 bytes of it. Verified with llama-tokenize on the VisionPsy vocab:
`<image: 0>` is [44, 5028, 42, 216, 32, 46], the same bytes plus a trailing NUL are
[44, 5028, 42, 216, 32, 46, 190], so every image in a multi-image prompt carried one garbage
token in a position the checkpoint never saw. common/common.cpp:455 is the same helper written
correctly; this brings clip's copy in line, which also drops the stray byte from every error
message built with it.

The sibling slice-delimiter template trims by hand for this reason
(mtmd.cpp:1234). Registered the coverage that was missing: the base VisionPsy row in tests.sh
now passes two images, since the label only appears above one image and both existing rows
passed a single --image. Confirmed locally, two images still answer "the new york times".

(cherry picked from commit e38010c8ef7af56a0f86cf6ca747969483218c05)

* QVAC-23075 fix: stop letterboxing the VisionPsy refined image

The projector inherited image_pad_rf = PAD_CEIL, so the refined image was aspect-preserved and
centred inside black bars, while the reference stretches straight to the target,
resize(img, [new_h, new_w]) in DynamicResize.forward. PAD_NONE with the existing bicubic algo
is that stretch. It bites whenever the refined aspect ratio differs from the original, which is
the shipped Flash default and needs no flag: 640x480 refines to 1024x512 and PAD_CEIL leaves
170 black columns on each side, a third of the encoded pixels; 1024x768 refines to 1024x1024
and gains 128 black rows top and bottom.

VisionPsy has its own hparams case, so idefics3 is untouched. HF's Idefics3ImageProcessor also
stretches, so it likely wants the same, but that is a pre-existing question and not this PR's.

Measured on Metal against the previous head: the Flash output changes on 960x720, 1024x768 and
640x480, exactly the sizes whose refined aspect ratio differs, and is unchanged on 256x256 and
on every base size, where the refine preserves the aspect ratio and PAD_CEIL was already adding
nothing. Slice and encode counts are identical throughout.

(cherry picked from commit e356a34e53ba125671f11f4d80f02755d72d1c76)

* QVAC-23075 fix: require the slicing sizes whenever VisionPsy loads, flag off included

The positivity guard only ran when no-upscale was on, but the base rule divides by the same two
values. With the flag off, image_size 0 reaches GGML_ASSERT(align_size > 0) in
calc_size_preserved_ratio and aborts the process at the first image instead of failing the
request, and image_longest_edge 0 makes the refined size {0,0}, so the grid is empty and the
model silently receives the overview alone.

Not extended to idefics3, which is where the reported version of this would have gone. The
shipped ggml-org/SmolVLM-500M-Instruct-GGUF mmproj carries no clip.vision.preproc_image_size
at all, so a throw there stops a model that loads today: verified, it failed to load with the
broader check. That model is already overview-only for exactly this reason, 1 image encode for
a 640x488 input, so it now gets a warning that says so rather than a hard failure. Also left
out of the global path, where image_size 0 legally means dynamic sizing per load_hparams' own
sanity check, so a blanket check would reject Qwen-VL.

Verified after the change: SmolVLM loads, warns, still answers "The New York Times"; VisionPsy
base loads and slices the same 17 encodes as before.

(cherry picked from commit 41266e2080a2ad3db898c14c6711b37c8b1a858b)

* QVAC-23075 test: golden tests for the idefics3 refined size and slice grid

The sizing rule decides how many slices an image becomes, and both bugs it carried into review
were invisible to the end-to-end tests, which only read the answer text. It is now a pure
function, mtmd_calc_idefics3_sizing, called by the preprocessor and checked directly by
tests/test-mtmd-preproc-sizing.cpp against values transcribed from the reference
DynamicResize._get_new_hw and evaluated in doubles. The Python used to produce them is in the
test's header comment.

25 cases: the 4:3 class and its transpose, which is what the float32 scale got wrong; small
square, where Flash lands on a single slice and base upscales to the cap; exact power-of-two
ratios, where nothing moved; the repo's own 640x488 test image, whose short side is under one
slice so Flash takes the enlarging branch; extreme aspect ratios; at and above the cap, where
the two rules converge; and a zero-size input. Each case also asserts the invariants the
splitter needs, both sides a whole number of slices and inside [image_size, cap].

Four of the expected slice counts are cross-checked against hardware: they plus one overview
equal the image-encode counts a local Metal run logs for the same inputs.

Confirmed the test catches the regression it was written for. Putting float32 back in
calc_size_preserved_ratio fails 960x720, 720x960 and 1920x1440 with a 4x4 grid where the
reference gives 4x3, while 1440x1080 still passes, which is why the class needs several
members rather than one.

(cherry picked from commit 8ee5417b1cdde90b174e37fbd2c73336ba060e87)

* QVAC-23075 test: pin the projector alias to general.name

The published VisionPsy mmprojs declare clip.projector_type = "custom", which any future model
could also pick, so the alias only resolves when general.name is VisionPsyNano as well. That
second condition is the safety property and it is one && away from being lost, with no visible
symptom until some unrelated "custom" mmproj loads as VisionPsy and preprocesses wrongly.

tests/test-clip-projector-alias.cpp checks the shipped pair resolves, that the same projector
string with any other name, an empty name, or a case-folded name stays unknown, that the right
name behind a different projector string is not the alias either, and that "custom" is absent
from the canonical table so it can only ever be reached through the name-gated path.

(cherry picked from commit ca62e1594f7f3af54cbfbab79bdcc70d253bd4eb)

* QVAC-23075 test: load-time coverage for the preprocessing metadata and the no-upscale override

tests/test-clip-preproc-metadata.cpp generates metadata-only mmprojs with the gguf writer and
runs them through clip_init, so it needs no committed fixture and no model download. That works
because the sizing check sits between load_hparams and load_tensors: a file that passes it still
fails, on a missing tensor, and each positive case asserts that specific failure so a load that
stopped earlier cannot masquerade as a pass.

13 cases. Rejected: image_size 0 and cap 0, each with the flag on and off, since the base rule
divides by the same values; and a cap below one slice, which is the std::clamp precondition.
Accepted: the shipped 512 and 2048 shape in both variants, and a cap of exactly one slice.

The rest covers the override. -1 leaves the GGUF value alone and logs no custom value, 0 and 1
both apply, turning it off against a GGUF that turned it on is announced because that is what a
zero-initialized params struct passes, and a projector that does not read the flag says it is
ignoring it. idefics3 without the cap key keeps loading and warns that slicing is off, which is
the state the shipped ggml-org/SmolVLM-500M-Instruct-GGUF mmproj is in.

(cherry picked from commit d52ab394a9bffd3fe8335d2188142969ec51f40d)

* QVAC-23075 test: check the VisionPsy tile structure in tests.sh, not just the answer

The answer text survives a wrong slice count, which is how the float32 sizing bug and the
letterboxed refine both passed this file. Rows can now declare how many image encodes they
expect, one per slice plus the overview, and the run fails when the count moves.

Both VisionPsy rows declare one. test-1.jpeg is 640x488: the base rule refines it to 2048x2048,
a 4x4 grid plus overview, and the row passes the image twice for the ordinal labels, so 34. The
Flash rule refines the same image to 1024x512, a 2x1 grid plus overview, so 3. Both counts
measured against the published checkpoints on Metal.

Only rows that declare a count are checked, so nothing else in the file changes.

(cherry picked from commit 8be2131e27d5589667db9c7f780fc1a9ef20869a)

* QVAC-23075 fix: reject a zero image_size on idefics3 too, not only VisionPsy

The previous guard was scoped to VisionPsy, so idefics3 kept the half of the finding that
aborts: image_size is the divisor and the align size of the shared rule, and zero reaches
GGML_ASSERT(align_size > 0) in calc_size_preserved_ratio at the first image, which kills the
process instead of failing the request. Nothing about that is VisionPsy specific, so both
projectors now fail the load.

The cap is the only half that legitimately differs. VisionPsy's published mmprojs all carry
clip.vision.preproc_image_size, so a missing cap there is broken metadata and throws, while
ggml-org/SmolVLM-500M-Instruct-GGUF carries none and would stop loading, so idefics3 keeps
the warning that says slicing is off.

Verified that SmolVLM still loads, still warns and still answers "The New York Times" on
tools/mtmd/test-1.jpeg, 1 encode as before. tests/test-clip-preproc-metadata.cpp gains the
idefics3 zero-image-size cases, both flag states, plus a valid idefics3 row so the new throw
cannot start rejecting a good file: 16 cases, all passing.

(cherry picked from commit 55b4a6fdceb73a59d8b94326a50755a3dbae1078)

* QVAC-23075 docs: say that libmtmd consumers must rebuild on every update

The ABI question raised in review: mtmd_context_params is passed and returned by value and
gains fields over time, so its size changes while SOVERSION stays 0. Upstream set SOVERSION 0
in #17091 and has appended fields in #17652, #24384 and #24865 without touching it, so
bumping it here would diverge for a case our consumers do not have, since they all build
fabric from source through a pinned vcpkg port. Documenting the requirement is the part that
costs nothing and is true regardless of how a consumer links.

(cherry picked from commit 24087591c936d03346c104a0fc9b6e82de17d26d)

* QVAC-23075 test: assert the VisionPsy prompt structure, not only the encode count

The QA request was for token and chunk sequence coverage, and the encode count does not give
it: it is blind to the ordinal labels, to the row and column delimiters and to where the
overview sits. tests.sh gains expect_log/expect_no_log over the -v prompt-assembly log, and
both VisionPsy rows now pin the whole sequence: the ordinals on the two-image row, the first
and last delimiter of each grid, the slice grid line, the chunk total, and the overview
position.

The overview delimiters are token ids rather than text, so nothing in the log said where the
overview went. Added the one debug line that says it, which also names the 1x1 case for what
it is, the refined slice standing in for the overview.

Both assertions were mutation tested against the local Metal build. Blanking ord_img_tmpl
leaves 34 encodes and chunk total 69 untouched and is caught only by the ordinal patterns.
Flipping ov_img_first leaves 3 encodes and total 7 untouched, still answers "the new york
times", and is caught only by the overview-position patterns. Chunk total 69 for the base row
is 2 x (ordinal text + overview + 16 x (delimiter + slice)) plus the trailing text chunk, and
7 for the Flash row.

Not covered, and not claimed: a 1x1 grid needs an image whose long side is at most 512 and the
only committed image is 640x488, so the single-tile path is asserted by
test-mtmd-preproc-sizing at the sizing level only.

(cherry picked from commit 295226e2d9067f88acfeb6184561d66e2366fc99)

* QVAC-23075 fix: format string_format into the result, not a vector

The NUL fix broke the GCC build. Returning std::string(buf.data(), size) over a
std::vector<char> keeps `size` live to the end of the function, and GCC then duplicates the
size == 0 path, where the vector is one byte, and reports -Wformat-truncation against it for
any caller whose format string carries a long literal. ubuntu-24.04-arm builds mtmd with
-Werror, so granite-speech.cpp failed on the "feature_layer_" literal.

Formatting straight into the std::string fixes both halves: the terminating NUL lands on the
byte past the end that the string already reserves for it, so it is still not part of the
result, and the destination extent is no longer something GCC can deduce, so the warning has
nothing to fire on. The size == 0 early return removes that path outright.

Token-identical on the case the NUL fix was about: the two-image VisionPsy prompt is 2240
prompt tokens before and after, still 34 image encodes, still answers "the new york times".

(cherry picked from commit d85b1f36711c9561e2a17b95491c1574e6e343d5)

* QVAC-23075 test: skip the two tests that need unexported symbols on Windows DLL builds

test-mtmd-preproc-sizing reaches mtmd_calc_idefics3_sizing and test-clip-preproc-metadata
reaches clip_init, clip_free and clip_log_set_callback. None of them carry MTMD_API, so
lld-link cannot resolve them across the Windows mtmd.dll boundary and every windows job failed
to link. clip.h says this outright at clip_fa_effective_min_kv, and tests/CMakeLists.txt
already guards a block of llama tests the same way, so use that guard rather than exporting
internals or letting the tests decide the library's export surface.

They still run on Linux and macOS, and on Windows in a static build, which is where the guard
condition ends. test-clip-projector-alias stays outside it: alias resolution is inline in
clip-impl.h, so it links everywhere.

(cherry picked from commit 4bccd82aaffcb03ae2ea3d1d0bd52fe10a8e00dd)

* squash! ci : functionally test the OpenCL ops on an Intel CPU ICD

ci : build the opencl test job with GGML_NATIVE=OFF

The ubuntu-24-opencl job flip-flops between pass and SIGILL (exit 132) on
unrelated commits. GGML_NATIVE defaults to ON for a native x86 build, so
-march=native is baked into the CPU backend objects, and the job's ccache
key is shared across runs. An object built on an AVX-512 Azure runner then
gets reused on a runner without it, and test-backend-ops dies with an
illegal instruction before printing any test output.

Every other x64 job in this repo already passes -DGGML_NATIVE=OFF.

(cherry picked from commit aff60f874b1e6520cd7964738f59077a45f13714)

* QVAC-23075 fix: bound the idefics3-style preprocessing metadata at load

The check added for VisionPsy rejected a cap of zero but nothing above it, and
preproc_image_size is a GGUF u32 read into an int. Both sizing rules upscale to
the cap, so the cap alone decides the grid: 512*195 gives a 195x195 grid at one
reserved tile each, and a cap near INT32_MAX overflows the multiply-back in
calc_size_preserved_ratio first. Bound the implied grid against
CLIP_PREPROC_MAX_TILES_LIMIT, the ceiling the Qwen-VL path already clamps to.

A cap that is not a whole number of slices was unchecked too. The slicing loop
steps by image_size and calc_size_no_upscale clamps the long side down to the
cap, so an off-grid cap emits a ragged trailing slice the reference splitter
never produces. test-mtmd-preproc-sizing already asserts that invariant, so
enforce it against real metadata as well.

The idefics3 overview-only warning and the no-upscale rejection were two
independent ifs, so a SmolVLM mmproj with no cap and --image-no-upscale on
logged "slicing is effectively off" and then threw. The later checks are else-if
now, so the warning describes what actually happens.

Only the VisionPsy case read clip.vision.preproc_no_upscale, while the override
and the CLI help both treat idefics3 as accepting the same rule, so an idefics3
GGUF declaring it was silently getting base preprocessing. Read the key there
too.

(cherry picked from commit 4ef2b3fdc0788d38e4e176030c07241ede40c5d0)

* opencl : fix Adreno MoE repack aliasing

Assisted-by: GPT-5.6 Sol
(cherry picked from commit e42aeed9b892e908fa58343a40751141a9d758fb)

* opencl : keep MoE repack words in GEMM order

Assisted-by: GPT-5.6 Sol
(cherry picked from commit 77c2bedd59a1724382d781c02b26d800d95a0a98)

* opencl : add CPU MoE repack diagnostic

Add an environment-gated host repack path to determine whether the Adreno E031.47 compiler still corrupts Q4_K trans4_ns conversion.

Assisted-by: GPT-5.6 Sol
(cherry picked from commit 8c03fb4e41b559f6bc9863fe73cd6b2b6fae099a)

* opencl : default MoE CPU repack on E031.47

Avoid the miscompiled Q4_K trans4 kernel on affected Qualcomm drivers while retaining an environment override for diagnostics.

(cherry picked from commit e36da3e1b63a20153fab7c279d2d3bc054dff909)

* opencl : detect Adreno 830 MoE repack workaround

Use the device name when Android omits the E031.47 compiler token from CL_DRIVER_VERSION so affected phones take the host repack path.

(cherry picked from commit f8749156fe3a6054e6f053191d886d2feb9968b8)

* opencl : disable Q4_K MoE MUL_MAT_ID on Adreno 8xx

The optimized Q4_K MoE kernels produce corrupted results on Adreno 8xx
(Adreno 830, Snapdragon 8 Elite): a MoE model generates garbage tokens
while the same weights are correct on the CPU backend.

Repacking the weights on the host instead of running
kernel_convert_block_q4_k_trans4_ns produces byte-identical corrupt
output, so the weight layout is not at fault - the matmul kernels are.
Declining GGML_OP_MUL_MAT_ID for Q4_K sends the op to the CPU backend,
which is the only configuration that yields correct output on this
hardware. The weight upload keeps using the MoE layout so other Adreno
generations and quantizations are unaffected.

The guard can be lifted per driver with GGML_OPENCL_ADRENO_Q4K_MOE=1.

Also drops the compiler-version heuristic from the host repack helper:
it is a diagnostic aid, not a fix, and stays opt-in via
GGML_OPENCL_Q4K_MOE_CPU_REPACK.

(cherry picked from commit 7f57e21d5edf57a05153cd7717280ef0ccdd0fd1)

* opencl : scope Q4_K MoE guard to the E031.47 compiler

Reading CL_DRIVER_VERSION off an Adreno 830 gives

  OpenCL 3.0 QUALCOMM build: 0800.74 Compiler E031.47.18.51

so the affected shader compiler is identifiable and the fallback does not
have to apply to every Adreno 8xx device indefinitely. Restrict it to
E031 compilers up to 47 and leave newer ones on the optimized kernels,
matching how adreno_e17_compiler_quirks scopes its workaround.

Also records that the dp4a kernels are corrupt on this driver too: they
produce different but equally invalid output, so preferring them is not a
way to keep the work on the GPU.

(cherry picked from commit 1c57effdbdcbc07835834b7f680e5b0977776ab6)

* opencl : extend MoE guard to every affected quantization

The Q4_K-only guard was narrower than the configuration that was
validated. A Samsung Galaxy S25 Ultra run with it still produced garbage:
the model is Q4_K_M, which carries 12 Q6_K and 6 Q5_0 tensors alongside
95 Q4_K ones, so 900 MiB of experts stayed on the optimized Adreno MoE
kernels while only the Q4_K ones moved to the CPU.

All of those quantizations share the same kernels, so decline the whole
Adreno-only MUL_MAT_ID branch instead of a single type. That matches the
configuration validated on an Adreno 830 via GGML_OPENCL_OPFILTER, which
declines every MUL_MAT_ID and produces correct output. Q4_0, Q8_0 and
MXFP4 have general MUL_MAT_ID support, are handled earlier, and keep
running on the GPU.

Renames the escape hatch to GGML_OPENCL_ADRENO_MOE_KERNELS to match the
wider scope.

(cherry picked from commit f761955d4f82d0b8d35e85d3a6043a8c53fd35c6)

* opencl : skip padded MoE output stores

The MoE GEMM kernels pointed padding slots at column 0 of the tile and
relied on writing column 0 last to overwrite them:

    if (idx == 0xFFFFFFFF) idx = src2[block_id_n * TILESIZE_N + 0];
    ...
    barrier(CLK_GLOBAL_MEM_FENCE);
    write_imagef(dst, out_idx[0] + m_offset, reg_c.s0);

dst is an image, and image stores are ordered by CLK_IMAGE_MEM_FENCE, not
CLK_GLOBAL_MEM_FENCE, so a driver is free to reorder or coalesce them. When
that happens column 0 of every padded tile keeps a padding accumulator
(zero) instead of its computed value, which silently drops one token's
expert output per tile. kernel_moe_fill pads every expert's last tile, so
prefill hits this constantly.

Keep the sentinel in out_idx and skip padded slots instead. Every store then
targets a distinct address and needs no ordering at all, and padded tiles
issue fewer image writes. ne01 is a multiple of 32 on this path, so a real
idx * ne01 is even and cannot collide with the odd sentinel.

Also move the tail-row return below the barrier: the global size along dim 0
is rounded up to TILESIZE_M, so returning above it let some work-items skip a
barrier the rest of the workgroup waits on.

Covers the quantizations exercised by Q4_K_M MoE models (Q4_K, Q5_0, Q6_K);
the remaining f32_ns variants share the pattern and follow separately.

(cherry picked from commit b78072f1db1e7065d75893541a4fdb61aec908fd)

* opencl : fix Adreno nibble-packing miscompile in trans4_ns repack

The Adreno E031.47 shader compiler miscompiles the two nibble-packing
helpers shared by every *_trans4_ns weight-repack kernel:

  - narrowing intermediates to uchar locals keeps only the first byte of
    each packed word and drops the rest;
  - the low-nibble path additionally loses the mask on the `<< 4` term, so
    the odd byte's high nibble leaks into the next byte of the word.

Because both helpers are shared, every MoE expert weight repacked for the
optimized kernels was silently corrupted regardless of quantization type,
which is why MoE models emitted garbage on Adreno 8xx. The matmul kernels
were not at fault.

Keep all intermediates in uint registers, mask each byte explicitly in
pack_uchar4, and combine low nibbles as `lo + hi * 16u` so no term can
exceed 8 bits even if a mask is elided.

Verified on an S25 Ultra (Adreno 830) by diffing the convert kernel's four
output buffers against a host reference repack: the quant buffer went from
54.2% of bytes wrong to bit-exact, with d/dm/s unaffected throughout. With
the optimized MoE kernels fully enabled, both a text and an OCR reproducer
now match their CPU baselines.

(cherry picked from commit 8286dfb9c5b447b38d30b663460020be46f513f2)

* opencl : fix get_scale_min_k4 miscompile in MoE matmul kernels

The same E031.47 compiler mishandles get_scale_min_k4() when it returns the
6-bit scale and min through pointers to private scalars, yielding random
noise for Q4_K/Q5_K MUL_MAT_ID. Return both values packed in a single uint
instead.

Takes MUL_MAT_ID failures on Adreno 830 from 76 to 0 in test-backend-ops.

(cherry picked from commit 396759ed796961e97531a62f886e99f2655c4310)

* test : cover Adreno MoE repack shapes in test-backend-ops

Add MUL_MAT_ID repack cases using the expert geometry of a real deployed
model (n_embd=1280, n_ff_exp=896, 6 of 64 experts) across both the GEMV
(decode) and GEMM (prefill) paths, which the existing small shapes missed.

Draw expert ids from a deterministic per-row permutation rather than an
ascending run, so the router table is exercised the way a real router drives
it, and report byte/block detail when a repack readback mismatches.

Stop forcing err() to 1.0 when the readback roundtrip fails: that masked the
real matmul error and hid whether the matmul itself was correct.

(cherry picked from commit 41633de416966cfa1d28015eac3f2252694c2b34)

* opencl : re-enable MoE MUL_MAT_ID on Adreno 8xx

The guard added earlier in this branch declined MUL_MAT_ID for every
quantization reaching the optimized MoE kernels on Adreno 8xx with the
E031.47 compiler, sending those ops to the CPU backend because that was the
only configuration known to produce correct output.

That was a mitigation for the two miscompiles fixed earlier in this branch,
so it is no longer needed. Its rationale was also wrong on the decisive
point: it concluded that host-side repacking reproduced the corruption bit
for bit and therefore the defect lay in the matmul kernels rather than the
weight layout. Host repacking in fact produces correct output, and the
defect was in the trans4_ns repack path.

Keeping the guard would fix the miscompile and then disable the code path it
fixes. Measured on a OnePlus CPH2723 (SM8750, Adreno 830, E031.47) with a
Q4_K_M MoE model and no environment overrides, decode goes from 3.15 to
74.55 tokens per second, a 23.7x speedup, while the generated text stays
byte-identical to the CPU-fallback baseline.

The compiler-version detection the guard used stays: it predates this
branch and backs unrelated feature checks.

(cherry picked from commit 040e0b2c37386936e109a83d526bd95ba52096e0)

* test : fail Adreno repack cases on a broken weight roundtrip

The Adreno repack cases reported a failed store/load roundtrip as an
informational note and returned the matmul error unchanged, so a corrupted
repack could still pass as long as that error stayed under the threshold.

The tolerance was added when the mismatch was believed to be a test-only
artifact of a raw read path. It was not — it was the same packing miscompile
fixed earlier in this branch. With the packing helpers corrected, all 80
adreno_trans4_ns cases roundtrip byte-exact on E031.47, so a mismatch is now
treated as the genuine failure it is.

(cherry picked from commit a8553a69209cba26032a3d39efc503f51a07f28b)

* squash! Vulkan: Add MUL_MAT_MAT and MUL_MAT_VEC support for TQ1

Vulkan: Fix TQ1/2 multi_mat_id pipeline

* ggml-cuda: implement gelu backward

Signed-off-by: makaveli10 <vineet.suryan@collabora.com>

* ggml-cuda: implement l2_norm backward

Signed-off-by: makaveli10 <vineet.suryan@collabora.com>

* ggml-vulkan: fix l2_norm_back for non-contiguous inputs

Signed-off-by: makaveli10 <vineet.suryan@collabora.com>

* ggml : add vector index C API foundation

Assisted-by: GPT-5.5

* ggml : harden vector-index foundation
Fix malformed snapshot handling, reserved padding IDs, finite input validation, score clamping, and PR1 test isolation. Make the vector-index foundation default-off behind a standalone  target.

* ggml : fix vector-index shared build coverage

Export vector-index symbols correctly in shared builds by propagating GGML_SHARED, and enable GGML_VECTOR_INDEX in Linux and Windows shared CI so the library and test target are exercised.

* ggml : harden vector-index snapshot tests
Reject non-zero reserved v1 header bytes, expand malformed snapshot and invalid API coverage, and fix ggml package configuration paths.

* ggml : harden vector index persistence and packaging
Make snapshot writes atomic, tighten API edge-case handling, and add CI coverage for package exports and static consumers.

* ggml : fix vector-index API and package coverage

* ggml : fix vector-index packaging and test coverage

* ggml : renumber vector-index error codes

Assisted-by: GPT-5.5

* ggml: harden vector index foundation
Fix vector-index API semantics, snapshot I/O safety, search ranking, and package smoke checks.

* ggml : fix vector-index snapshot file handling

Avoid GCC attribute warnings from the FILE deleter and keep POSIX snapshot writes restrictive until publish. Preserve existing file modes, apply default creation permissions for new files, and cover the permission behavior in tests.

* ggml-vector-index : amortize add capacity growth
Avoid exact reserve calls during add by growing storage capacity with slack and only reserving the id map when insertion would rehash.

* ggml-vector-index : harden snapshot rename durability

Sync the temporary snapshot again after chmod, fsync the parent directory after rename, and report post-rename sync failures as GGML_VEC_INDEX_E_NOT_DURABLE.

* ggml : reject oversized vector index snapshots on write

* ggml : add vector index snapshot helper header

Assisted-by: GPT-5.5

* ggml : harden vector-index durability and packaging
Fix vector-index snapshot durability on Windows and macOS, tighten byte-span overflow validation, and cover static package consumers in CI.

* ggml : harden vector-index durability and packaging
Fix vector-index snapshot durability on Windows and macOS, tighten byte-span overflow validation, and cover static package consumers in CI.

* tests : cover failed vector-index snapshot overwrite
Add regression coverage that a failed atomic write targeting an existing valid snapshot leaves the snapshot bytes unchanged.

* Fixing metal run issues in CI by now uses the venv Python for both pip installs and the jinja2 import check.

* Revert "Fixing metal run issues in CI by now uses the venv Python for both pip installs and the jinja2 import check."

This reverts commit 7be3d08b1934e0bdb3cc91acb743f252ccf500b8.

Assisted-by: GPT-5.5

* ci : use venv python for model conversion

Run converter installs and imports through the CI virtualenv interpreter so macOS self-hosted jobs do not fall back to a Python without jinja2 or torch.

Assisted-by: GPT-5.5

* ci : revert venv python conversion

Restore the CI script after confirming the Apple GPU failure comes from the self-hosted runner Python version rather than converter interpreter selection.

Assisted-by: GPT-5.5

* ggml : add q4 q8 vector index search

Assisted-by: GPT-5.5

* ggml : restore vector-index utility APIs

Assisted-by: GPT-5.5

* ggml : reject trailing vector-index snapshots

Assisted-…
JenySadadia pushed a commit to JenySadadia/qvac-fabric-llm.cpp that referenced this pull request Sep 7, 2026
The upstream target of this former fixup! (50e0ad0, --clear-idle
ggml-org#20993) already landed upstream, so this stays a standalone commit.

Relying on exact log text is brittle, especially across rebases with
upstream changes; use the timings fields instead and drain remaining
logs for test cleanliness.

(cherry picked from commit 1df9cb1)
(cherry picked from commit 96bb34a)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

examples merge ready A maintainer can use this label to indicate that they consider the changes final and ready to merge. python python script changes server

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants