From 863897f92a1a759d77a3807a8cc601c19192c0d3 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 25 Aug 2026 19:07:46 -0700 Subject: [PATCH] Clarify llama-bench harness scope --- docs/.obsidian/app.json | 2 +- docs/LLM/index.md | 2 +- docs/LLM/ubatch-benchmark-large.md | 47 +++--- docs/index.md | 2 +- scripts/bench-fast-greedy.sh | 77 --------- scripts/bench-kairic-ab.sh | 153 ------------------ scripts/bench-spec-stack.sh | 191 ---------------------- scripts/bench-ubatch-rocmi4.sh | 245 ----------------------------- scripts/bench-ubatch.sh | 45 ++++++ tests/frank_large_config.bats | 32 ++-- 10 files changed, 78 insertions(+), 718 deletions(-) delete mode 100755 scripts/bench-fast-greedy.sh delete mode 100755 scripts/bench-kairic-ab.sh delete mode 100755 scripts/bench-spec-stack.sh delete mode 100755 scripts/bench-ubatch-rocmi4.sh create mode 100755 scripts/bench-ubatch.sh diff --git a/docs/.obsidian/app.json b/docs/.obsidian/app.json index c9ee2b7..baeee86 100644 --- a/docs/.obsidian/app.json +++ b/docs/.obsidian/app.json @@ -5,4 +5,4 @@ "attachmentFolderPath": "_attachments", "useMarkdownLinks": false, "showUnsupportedFiles": true -} +} \ No newline at end of file diff --git a/docs/LLM/index.md b/docs/LLM/index.md index b4ebf7d..836e177 100644 --- a/docs/LLM/index.md +++ b/docs/LLM/index.md @@ -50,7 +50,7 @@ MTP draft + Turbo4 V cache, 262K ctx / 1 slot] - [[frank-redo-2026-08-17]] — the 2026-08-17 topology change - [kingjones Qwen3.8 Uncensored ROCmFP4 STRIX MTP](https://huggingface.co/kingjones777/Qwen3.8-27B-Uncensored-ROCmFP4-STRIX-MTP-GGUF) — active frank-default-large model on Dankclaw; Q6 AGENT main model with standalone MTP - [[kingjones-strix-mtp]] — the active Q6 AGENT runbook: serving profile, standalone MTP, reasoning + preserve_thinking, 114 GiB TTM/GTT -- [[ubatch-benchmark-large]] — active-model ubatch sweep plan and results +- [[ubatch-benchmark-large]] — active-model llama-bench ubatch sweep plan and results - [[kingjones-q8agent-rocmfpx]] — the retired Q8_0-AGENT investigation and `set_rows` failure - [[eemin-rocmi4-mtp]] — the former caf-eemin Qwen3.8-27B embedded-MTP model on Dankclaw (retired) diff --git a/docs/LLM/ubatch-benchmark-large.md b/docs/LLM/ubatch-benchmark-large.md index 6fac208..68ed540 100644 --- a/docs/LLM/ubatch-benchmark-large.md +++ b/docs/LLM/ubatch-benchmark-large.md @@ -12,13 +12,13 @@ timestamp: 2026-09-01 # frank-default-large ubatch sweep -This benchmark sweeps the active `frank-default-large` model on Dankclaw using -scratch ports and a 32K-context workload. The production service is stopped -before the sweep and restarted by the benchmark's exit handler. +This benchmark uses the active `frank-default-large` model on Dankclaw with +`llama-bench`. It does not use `llama-server`, HTTP requests, or server-log +scraping. Stop the production service before running it and start the service +again afterward. -The 2026-09-01 run used `ctx=32768` and `batch=32768`. It completed through -`ubatch=3072`; `ubatch=4096` failed during initialization with a ROCm -allocation error, so 4096, 5120, and 6144 were not run. +The old custom harnesses are retained in `.AGENTTRASH/bench-scripts-20260901/` +for auditability only. Their results are invalid and must not be used. ## Sweep @@ -30,32 +30,25 @@ requests by default. Run on Dankclaw from the repository checkout: ```bash -STOPSVC=1 ./scripts/bench-ubatch-rocmi4.sh +sudo systemctl stop frank-default-large +./scripts/bench-ubatch.sh +sudo systemctl start frank-default-large ``` -The script uses the active Q6 AGENT model, standalone MTP draft, a fixed -32K batch, and the tracked Sharp -template, and `--reasoning-format deepseek`. It reports server timing lines and -per-run generation throughput. Run only when the production service can be -interrupted; the GPU is shared with live workloads. +The script uses the active Q6 AGENT model, a fixed 16K prompt workload, and +`llama-bench` JSONL output. The initial ubatch-only sweep is: +```text +256, 512, 768, 1024, 1280, 1536, 1792, 2048, 3072, 4096, 5120, 6144 +``` + +The GPU is shared with live workloads; do not run with the production service +up. ## Results -Record results below after the sweep, including failures or OOM points. - -| ubatch | cold prefill | generation runs 1/2/3 | notes | -| ---: | ---: | ---: | --- | -| 256 | 214.73 tok/s | 17.68 / 17.15 / 17.12 tok/s | completed | -| 512 | 213.47 tok/s | 17.91 / 17.29 / 17.72 tok/s | completed | -| 768 | 215.60 tok/s | 17.96 / 18.17 / 17.98 tok/s | completed | -| 1024 | 222.20 tok/s | 18.07 / 18.10 / 18.12 tok/s | completed | -| 1280 | 219.13 tok/s | 17.47 / 17.69 / 17.49 tok/s | completed | -| 1536 | 220.52 tok/s | 16.85 / 16.86 / 16.86 tok/s | completed | -| 2048 | 60.97 tok/s | 6.24 / 4.94 / 11.19 tok/s | severe slowdown | -| 3072 | 28.50 tok/s | 1.45 / 5.03 / 4.44 tok/s | severe slowdown | -| 4096 | — | — | initialization OOM: rs-cache allocation | -| 5120 | — | — | not reached | -| 6144 | — | — | not reached | +No valid results are recorded here. All results from the retired custom +harnesses are explicitly discarded. Record the raw `llama-bench` JSONL output +from the new harness here after a clean run. ## See also diff --git a/docs/index.md b/docs/index.md index 504cad6..0537829 100644 --- a/docs/index.md +++ b/docs/index.md @@ -119,7 +119,7 @@ The LLM inference fleet on the two hosts. Full index: ### Benchmarks and Sizing - [[frank-default-large-benchmark]] — backend/spec benchmark behind the old head -- [[ubatch-benchmark-large]] — active-model ubatch sweep plan and results +- [[ubatch-benchmark-large]] — active-model llama-bench ubatch sweep plan and results - [[frank-default-small-benchmark]] — the small-model benchmark suite - [[Running Qwen3.6 35B A3B FP8 on Dankserver]] — Qwen3.6-35B-A3B hardware notes - [[Running Qwen3.6 27B FP8 on Dankclaw]] — Qwen3.6-27B sizing on the iGPU diff --git a/scripts/bench-fast-greedy.sh b/scripts/bench-fast-greedy.sh deleted file mode 100755 index 19f4d5a..0000000 --- a/scripts/bench-fast-greedy.sh +++ /dev/null @@ -1,77 +0,0 @@ -#!/usr/bin/env bash -# Bench the Kairic Edge "fast greedy (default)" profile. -# Usage: ./bench-fast-greedy.sh -# -# Config: reasoning off, argmax fastpath=1, deterministic sampler. -# Sends a coding prompt, captures full stream, computes decode tokens and time. -set -euo pipefail - -HOST="127.0.0.1" -PORT=7770 -ALIAS="frank-default-large" - -# A coding prompt representative of the card's 164-task suite. -PROMPT='### Instruction -Write a Python class that implements a priority queue using a binary heap. Include methods for enqueue, dequeue, peek, and is_empty. Add type hints, docstrings, and a usage example. The heap should support arbitrary comparable elements. -### Response' - -DECODE_TOKENS=0 -START_SEC=0 -END_SEC=0 - -echo "=== Fast-greedy benchmark (FASTGREEDY=1) ===" -echo "Model: $ALIAS on $HOST:$PORT" -echo "Prompt tokens: $(echo "$PROMPT" | wc -c) chars" -echo "" - -# Use /v1/completions with streaming, capture tokens+time from usage. -RESPONSE=$(curl -sf http://$HOST:$PORT/v1/completions \ - -H 'Content-Type: application/json' \ - -d "{ - \"model\": \"$ALIAS\", - \"prompt\": $(python3 -c "import json,sys; print(json.dumps(sys.stdin.read()))" <<< "$PROMPT"), - \"max_tokens\": 512, - \"temperature\": 0, - \"top_p\": 1.0, - \"top_k\": 0, - \"min_p\": 0.0, - \"n\": 1, - \"stream\": true - }" 2>/dev/null) - -# Extract final usage from the last JSON line (finish_reason=stop or length). -# Also count output text tokens. -LAST_LINE=$(echo "$RESPONSE" | grep 'finish_reason' | tail -1) - -# Parse timing from the last chunk (has full usage) -if echo "$LAST_LINE" | grep -q 'timings'; then - COMP_TOKENS=$(echo "$LAST_LINE" | python3 -c " -import sys, json -data = json.loads(sys.stdin.read()) -print(data.get('usage', {}).get('completion_tokens', 0)) -print(data.get('timings', {}).get('predicted_per_second', 0)) -print(data.get('timings', {}).get('prompt_per_second', 0)) -" 2>/dev/null) - echo "$COMP_TOKENS" | { - read CT - read TPS - read PPS - echo "Completion tokens: $CT" - echo "Predicted (decode) tokens/sec: $TPS" - echo "Prompt tokens/sec: $PPS" - } -else - echo "WARNING: No timing data found in response" - echo "$LAST_LINE" | head -5 -fi - -# Count the actual text output -OUTPUT_TEXT=$(echo "$RESPONSE" | grep '"text"' | head -1 | sed 's/.*"text":"//;s/","index.*//') -OUTPUT_TOKENS=$(echo -n "$OUTPUT_TEXT" | wc -w) -echo "Output text (rough token count): ~$OUTPUT_TOKENS words" -echo "" - -# Check journal for draft acceptance and final timing -echo "=== Draft acceptance & final timing ===" -sleep 2 -ssh -p 2222 dank@dankclaw "journalctl -u frank-default-large --since '1 minute ago' --no-pager 2>/dev/null | grep -E 'draft acceptance|print_timing.*n_decoded|statistics.*draft-mtp' | tail -4" 2>/dev/null || echo "(could not read journal)" diff --git a/scripts/bench-kairic-ab.sh b/scripts/bench-kairic-ab.sh deleted file mode 100755 index fe1f4e0..0000000 --- a/scripts/bench-kairic-ab.sh +++ /dev/null @@ -1,153 +0,0 @@ -#!/usr/bin/env bash -# bench-kairic-ab.sh — A/B perf isolation for the Kairic Edge deployment on -# Dankclaw. Run this ON DANKCLAW (paths are absolute target paths). -# -# Configs: -# runner pinned runner profile (b2048/ub512, reasoning off, target -# greedy argmax fast path ON) -# nommap runner + --no-mmap -# nofast runner + fast path OFF -# prod production profile (b4096/ub4096, --no-mmap, reasoning on/deepseek, -# fast path OFF, preserve_thinking) -# prod2048 prod with the pinned runner's batch sizes (b2048/ub512) -# prodnommap (obsolete: identical to prod since ubatch 4096 + --no-mmap -# moved into the production profile) -# -# Usage: ./scripts/bench-kairic-ab.sh [ctx] [prompt_chars] -# Example: ./scripts/bench-kairic-ab.sh prod 16384 53000 -# -# The server runs on scratch port 7893. STOP frank-default-large before -# benchmarking (see AGENTS.md). The server is killed on exit. -set -uo pipefail -CFG="${1:?runner|nofast|prod}" -CTX="${2:-32768}" -PCHARS="${3:-11000}" -PORT=7893 -BIN=/home/dank/ciru-qwen38/ROCmFPX-kairic/build-kairic/bin/llama-server -MDIR=/home/dank/models/Kairic-Qwen3.8-27B-IU4-Edge -SDK=/home/dank/ling3-ciru/Ling-3.0-Flash-CIRU-int4-Strix-native/.venv/lib/python3.12/site-packages -LOG=/tmp/kairic-ab-$CFG.log -: > "$LOG" - -export ROCM_PATH="$SDK/_rocm_sdk_devel" -export LD_LIBRARY_PATH="$(dirname "$BIN"):$SDK/_rocm_sdk_libraries/lib:$SDK/_rocm_sdk_core/lib:$SDK/_rocm_sdk_devel/lib" -export HIP_VISIBLE_DEVICES=0 -export HSA_OVERRIDE_GFX_VERSION=11.5.1 -export GGML_CUDA_GRAPH_OPT=0 -export LLAMA_MTP_CPU_ARGMAX_FASTPATH=1 -export PROMPTFORGE_TARGET_ONLY=0 -export PROMPTFORGE_MODE=iu4_ffn -export PROMPTFORGE_IU4_SIDECAR="$MDIR/Qwen3.8-27B-Kairic-IU4-FFN.pfs" -export PROMPTFORGE_SIDECAR="$MDIR/Qwen3.8-27B-Kairic-IU4-FFN.pfs" -export PROMPTFORGE_IU4_HADAMARD=1 -export PROMPTFORGE_IU4_SEGMENTED=0 -export PROMPTFORGE_ENABLE_FFN_KEEPERS=0 -export PROMPTFORGE_FFN_KEEPERS=late6 -export PROMPTFORGE_ENABLE_GDN=1 -export PROMPTFORGE_ENABLE_GDN_W8=0 -export PROMPTFORGE_GDN_SIDECAR_OVERRIDE="$MDIR/Qwen3.8-27B-Kairic-IU4-GDN.pfs" -export PROMPTFORGE_GDN_SIDECAR="$MDIR/Qwen3.8-27B-Kairic-IU4-GDN.pfs" -export PROMPTFORGE_GDN_IU4_HADAMARD=1 -export PROMPTFORGE_ENABLE_GDN_KEEPERS=0 -export PROMPTFORGE_ENABLE_GDN_OUTPUT=1 -export PROMPTFORGE_GDN_OUTPUT_SIDECAR="$MDIR/Qwen3.8-27B-Kairic-IU4-GDN-Output.pfs" -export PROMPTFORGE_GDN_OUTPUT_KEEPERS=v3_lateq6 -export PROMPTFORGE_ENABLE_SMALLM_IU4=1 -export PROMPTFORGE_ENABLE_SMALLM_GDN_IU4=1 -export PROMPTFORGE_ENABLE_SMALLM_GDN_OUTPUT_IU4=0 -export PROMPTFORGE_ENABLE_IU4_DECODE_M2_M5=0 -export PROMPTFORGE_OUTPUT_K8_STRICT_GREEDY=0 - -MMAP="" -case "$CFG" in - runner) export LLAMA_TARGET_GREEDY_ARGMAX_FASTPATH=1; B=2048; UB=512; REA=off; RFMT=none; EXTRA="" ;; - nommap) export LLAMA_TARGET_GREEDY_ARGMAX_FASTPATH=1; B=2048; UB=512; REA=off; RFMT=none; EXTRA=""; MMAP="--no-mmap" ;; - nofast) export LLAMA_TARGET_GREEDY_ARGMAX_FASTPATH=0; B=2048; UB=512; REA=off; RFMT=none; EXTRA="" ;; - prod) export LLAMA_TARGET_GREEDY_ARGMAX_FASTPATH=0; B=4096; UB=4096; REA=on; RFMT=deepseek; EXTRA="--chat-template-kwargs {\"preserve_thinking\":\"true\"}"; MMAP="--no-mmap" ;; - prod2048) export LLAMA_TARGET_GREEDY_ARGMAX_FASTPATH=0; B=2048; UB=512; REA=on; RFMT=deepseek; EXTRA="--chat-template-kwargs {\"preserve_thinking\":\"true\"}"; MMAP="--no-mmap" ;; - prodnommap) export LLAMA_TARGET_GREEDY_ARGMAX_FASTPATH=0; B=4096; UB=2048; REA=on; RFMT=deepseek; EXTRA="--chat-template-kwargs {\"preserve_thinking\":\"true\"}"; MMAP="--no-mmap" ;; -esac - -"$BIN" -m "$MDIR/Qwen3.8-27B-IU4-Kairic-Edge.gguf" \ - --alias bench --host 127.0.0.1 --port "$PORT" --jinja \ - -dev ROCm0 -ngl 999 -c "$CTX" -b "$B" -ub "$UB" -fa on -ctk f16 -ctv f16 \ - -t 16 -tb 32 -np 1 -ctxcp 32 --cache-ram 8192 --cache-prompt --cache-idle-slots $MMAP \ - --metrics --kairic-edge \ - --spec-type draft-mtp --spec-draft-device ROCm0 --spec-draft-ngl 999 \ - --spec-draft-type-k f16 --spec-draft-type-v f16 \ - --spec-draft-n-max 4 --spec-draft-n-min 0 --spec-draft-p-min 0.0 --spec-draft-p-split 0.10 \ - --spec-draft-backend-sampling \ - --temp 0 --top-p 1.0 --top-k 0 --min-p 0.0 \ - --reasoning "$REA" --reasoning-format "$RFMT" --reasoning-budget -1 \ - $EXTRA >> "$LOG" 2>&1 & -SPID=$! -trap 'kill $SPID 2>/dev/null; wait $SPID 2>/dev/null' EXIT - -for i in $(seq 1 300); do - kill -0 $SPID 2>/dev/null || { echo "SERVER DIED"; tail -20 "$LOG"; exit 2; } - curl -sf "http://127.0.0.1:$PORT/health" >/dev/null 2>&1 && break - sleep 2 -done -echo "=== CFG=$CFG ready ===" - -python3 - "$PORT" "$CFG" "$PCHARS" <<'PY' -import sys, json, time, urllib.request -port, cfg, pchars = sys.argv[1], sys.argv[2], int(sys.argv[3]) -VAR = [ - "narrative nonfiction blends observation with reflection, ", - "and a careful writer lets each clause earn its place ", - "in the sentence's rising line, so meaning accumulates slowly. ", - "The reader is not hurried; the reader is led. Loose and periodic, ", - "the style turns on concrete nouns and verbs that carry weight. ", - "A paragraph is not a list but a single turn of thought that ", - "opens, develops, and lands, and clarity is a courtesy owed ", - "to the willing reader who gives the writer their attention. ", -] -parts = [] -t = 0 -while len("".join(parts)) < pchars: - for k in range(3): - parts.append(VAR[(t + k) % len(VAR)]) - parts.append(f" Note {t}. ") - t += 1 -longtext = "".join(parts) -prompt = longtext + "\n\nNow write a long, detailed continuation of this essay (at least five paragraphs) about the philosophy of language.\n" -body = { - "model": "bench", - "prompt": prompt, - "max_tokens": 512, - "temperature": 0, - "top_p": 1.0, - "top_k": 0, - "min_p": 0.0, - "stream": False, - "cache_prompt": True, -} -# ignore_eos is rejected by the exact-Q8 greedy argmax fast path (runner cfg). -if cfg not in ("runner",): - body["ignore_eos"] = True -def run(tag): - req = urllib.request.Request(f"http://127.0.0.1:{port}/v1/completions", - data=json.dumps(body).encode(), - headers={"Content-Type": "application/json"}) - t0 = time.time() - try: - with urllib.request.urlopen(req, timeout=3600) as resp: - out = json.load(resp) - except Exception as e: - body_txt = getattr(e, "read", lambda: b"")() - print(f"[{tag}] REQUEST FAILED: {e}") - print(f"[{tag}] body: {body_txt.decode('utf-8', 'ignore')[:500]}") - raise SystemExit(3) - wall = time.time() - t0 - u = out.get("usage", {}) - tm = out.get("timings", {}) - pt = u.get("prompt_tokens", 0); ct = u.get("completion_tokens", 0) - pps = tm.get("prompt_per_second", 0); tps = tm.get("predicted_per_second", 0) - print(f"[{tag}] cfg={cfg} prompt={pt} completion={ct} prefill={pps:.1f} t/s decode={tps:.2f} t/s wall={wall:.1f}s") -run("cold") -run("warm") -PY - -grep -E "print_timing|statistics" "$LOG" | tail -6 -exit 0 diff --git a/scripts/bench-spec-stack.sh b/scripts/bench-spec-stack.sh deleted file mode 100755 index 857a003..0000000 --- a/scripts/bench-spec-stack.sh +++ /dev/null @@ -1,191 +0,0 @@ -#!/usr/bin/env bash -# bench-spec-stack.sh — compare MTP-only vs MTP+ngram-mod spec on the v3 -# CIRU prompt-forge stack (Dankclaw, gfx1151), at 8k / 16k / 64k context. -# -# Reads authoritative prefill/generation tok/s from llama-server's own -# `print_timing` lines (prompt eval time / eval time). Runs one server -# instance per (spec, ctx) on a scratch port. -# -# Usage: -# ./scripts/bench-spec-stack.sh [out_tokens] -# Example: -# ./scripts/bench-spec-stack.sh draft-mtp,ngram-mod 16384 256 -set -euo pipefail - -SPEC="${1:?usage: bench-spec-stack.sh [out_tokens]}" -CTX="${2:?usage: bench-spec-stack.sh [out_tokens]}" -OUT_TOKENS="${3:-256}" - -ENV_FILE=/home/dank/serverAppConfigs/scripts/frank-default-large.env -source "$ENV_FILE" - -# This harness benchmarks the retired v3 stack. Force the v3 values so the -# harness works regardless of the live service env (now Kairic). -MODEL_DIR=/home/dank/models/CIRU-Qwen3.8-27B-ActiveFPX-v3 -MODEL_FILE=Qwen3.8-27B-CIRU-ActiveFPX-PromptForge-v3-Q8-FFNEQ-LateQ6.gguf -FFN_PFS=Qwen3.8-27B-CIRU-ActiveFPX-PromptForge-FFN.pfs -GDN_PFS=Qwen3.8-27B-CIRU-ActiveFPX-PromptForge-GDN.pfs -OUTPUT_K8_PFS=Qwen3.8-27B-CIRU-ActiveFPX-PromptForge-Output-K8.pfs -MMPROJ_FILE=Qwen3.8-27B-mmproj-BF16.mmproj - -# Derive the PromptForge sidecar paths from the env base values. -export PROMPTFORGE_SIDECAR="$MODEL_DIR/$FFN_PFS" -export PROMPTFORGE_GDN_SIDECAR="$MODEL_DIR/$GDN_PFS" -export PROMPTFORGE_MTP_OUTPUT_K8_PROXY="$MODEL_DIR/$OUTPUT_K8_PFS" - -MODEL="$MODEL_DIR/$MODEL_FILE" -LLAMA_BIN=/home/dank/ciru-qwen38/ROCmFPX/build-promptforge/bin/llama-server -PORT=7893 -PARALLEL=1 -BATCH=4096 -UBATCH=4096 -LOG="/tmp/specstack-$(echo "$SPEC" | tr ',' '-')-ctx$CTX.log" -: > "$LOG" - -for f in "$MODEL" "$PROMPTFORGE_SIDECAR" "$PROMPTFORGE_GDN_SIDECAR" "$PROMPTFORGE_MTP_OUTPUT_K8_PROXY" "$MODEL_DIR/$MMPROJ_FILE"; do - [[ -f "$f" ]] || { echo "missing: $f" >&2; exit 1; } -done -[[ -x "$LLAMA_BIN" ]] || { echo "missing: $LLAMA_BIN" >&2; exit 1; } - -export PROMPTFORGE_MODE="m2048_fused_tail1476" -export GGML_CUDA_GRAPH_OPT=0 -export PROMPTFORGE_ENABLE_SMALLM_W8=1 -export PROMPTFORGE_MTP_OUTPUT_K8=1 -export PROMPTFORGE_MTP_OUTPUT_K8_VALIDATE=0 -export PROMPTFORGE_MTP_OUTPUT_K8_PROXY -unset LLAMA_MTP_CPU_ARGMAX_FASTPATH LLAMA_MTP_BACKEND_GREEDY || true -unset PROMPTFORGE_IU4_SIDECAR PROMPTFORGE_GDN_IU4_SIDECAR || true -unset PROMPTFORGE_ENABLE_NGRAM_MOD PROMPTFORGE_ENABLE_NGRAM_M65_IU4 || true - -export ROCM_PATH="$ROCM_DEVEL" -export LD_LIBRARY_PATH="${ROCM_LIB}/lib:${ROCM_CORE}/lib:${ROCM_DEVEL}/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" - -echo "=== spec-stack bench: spec=$SPEC ctx=$CTX model=$MODEL_FILE ===" -echo "=== env: OUTPUT_K8=$PROMPTFORGE_MTP_OUTPUT_K8 smallm=$PROMPTFORGE_ENABLE_SMALLM_W8 ===" - -# ngram-mod tuning matches the retired fallback line: n_min 48 / n_max 64 / n_match 24 -"$LLAMA_BIN" \ - --model "$MODEL" \ - --mmproj "$MODEL_DIR/$MMPROJ_FILE" \ - --alias bench \ - --host 127.0.0.1 \ - --port "$PORT" \ - -dev ROCm0 \ - -ngl 999 \ - -c "$CTX" \ - -b "$BATCH" \ - -ub "$UBATCH" \ - -fa on \ - -ctk "$CACHE_TYPE_K" \ - -ctv "$CACHE_TYPE_V" \ - -np "$PARALLEL" \ - -ctxcp "$CTX_CHECKPOINTS" \ - --cache-ram "$CACHE_RAM" \ - --cache-prompt \ - --no-mmap \ - --spec-type "$SPEC" \ - --spec-draft-device ROCm0 \ - --spec-draft-ngl 999 \ - --spec-draft-type-k f16 \ - --spec-draft-type-v f16 \ - --spec-draft-n-max 4 \ - --spec-draft-n-min 0 \ - --spec-draft-p-min 0.0 \ - --spec-draft-p-split 0.10 \ - --spec-draft-backend-sampling \ - --spec-ngram-mod-n-min 48 \ - --spec-ngram-mod-n-max 64 \ - --spec-ngram-mod-n-match 24 \ - --temp 0.7 \ - --top-p 0.95 \ - -lv 4 \ - >> "$LOG" 2>&1 & -SERVER_PID=$! -trap 'kill $SERVER_PID 2>/dev/null; wait $SERVER_PID 2>/dev/null' EXIT - -echo "server pid $SERVER_PID on 127.0.0.1:$PORT" -echo -n "waiting for server" -ready=0 -crashed=0 -for i in $(seq 1 900); do - if ! kill -0 "$SERVER_PID" 2>/dev/null; then - crashed=1 - break - fi - if curl -sf "http://127.0.0.1:$PORT/health" >/dev/null 2>&1; then - echo " [ready]" - ready=1 - break - fi - [ $((i % 10)) = 0 ] && echo -n . - sleep 2 -done -if [ "$crashed" = "1" ]; then - echo " BACKEND_CRASHED (server died before ready)" - tail -30 "$LOG" - exit 2 -fi -[ "$ready" = "1" ] || { echo " FAIL (timeout)"; tail -25 "$LOG"; exit 1; } - -# Print the implementations that initialized (feasibility evidence) -grep -E "adding speculative implementation|ngram_mod|draft-mtp" "$LOG" | head -6 - -python3 - "$SPEC" "$CTX" "$PORT" "$OUT_TOKENS" "$LOG" <<'PY' -import sys, time, json, urllib.request -spec, ctx, port, out_tokens, log = sys.argv[1], int(sys.argv[2]), sys.argv[3], int(sys.argv[4]), sys.argv[5] - -# Flowing prose (varied) at ~75% of ctx so MTP acceptance is realistic. -VAR = ( - "narrative nonfiction blends observation with reflection, ", - "and a careful writer lets each clause earn its place ", - "in the sentence's rising line, so meaning accumulates slowly. ", - "The reader is not hurried; the reader is led. Loose and periodic, ", - "the style turns on concrete nouns and verbs that carry weight, ", - "and it resists the abstract noun that hides a vague claim. ", - "A paragraph is not a list. It is a single turn of thought that ", - "opens, develops, and lands. This is the craft of prose. Choose, ", - "arrange, and above all omit. Omission is the discipline that ", - "keeps the whole clear, and clarity is the courtesy we owe the ", - "willing reader." -) -n = len(VAR) -parts = [] -t = 0 -while t * 76 < (ctx * 3) // 4: - for k in range(5): - parts.append(VAR[(t + k) % n]) - parts.append(f" This is cued by note {t}. ") - t += 1 -prompt = "".join(parts) -prompt += ("\n\nNow, in a detailed and flowing paragraph, continue this essay about " - "the philosophy of language and its influence on modern computation. ") - -body = json.dumps({ - "prompt": prompt, - "max_tokens": out_tokens, - "temperature": 0.0, - "stream": False, -}).encode() -req = urllib.request.Request(f"http://127.0.0.1:{port}/v1/completions", - data=body, headers={"Content-Type": "application/json"}) -t0 = time.time() -with urllib.request.urlopen(req, timeout=7200) as resp: - out = json.load(resp) -dt = time.time() - t0 -pt = out.get("usage", {}).get("prompt_tokens", 0) -ct = out.get("usage", {}).get("completion_tokens", 0) -print(f"request done: prompt_tokens={pt} completion_tokens={ct} wall={dt:.1f}s", flush=True) - -lines = open(log, encoding="utf-8", errors="ignore").read().splitlines() -prompt_evals = [l for l in lines if "prompt eval time" in l] -evals = [l for l in lines if "eval time" in l and "prompt eval" not in l] - -def grab(lines, label): - if lines: - print(label + ": " + lines[-1].strip()) - -print(f"== RESULT spec={spec} ctx={ctx} ==") -grab(prompt_evals, "PREFILL") -grab(evals, "GENERATION") -print(f"prompt_tokens={pt} completion_tokens={ct} wall={dt:.1f}s") -PY \ No newline at end of file diff --git a/scripts/bench-ubatch-rocmi4.sh b/scripts/bench-ubatch-rocmi4.sh deleted file mode 100755 index 997efcd..0000000 --- a/scripts/bench-ubatch-rocmi4.sh +++ /dev/null @@ -1,245 +0,0 @@ -#!/usr/bin/env bash -# bench-ubatch-rocmi4.sh — sweep batch size with a fixed ubatch at 16k context -# on the frank-default-large ROCmFPX standalone-MTP stack (dankclaw, gfx1151). -# -# Boots one llama-server instance per batch on a scratch port with a fixed -# ubatch and -c 16384, runs a standardized ~75%-of-ctx prose workload, and reads -# authoritative prefill/generation tok/s from the server's own print_timing -# lines. Steps from UBATCH_MIN to UBATCH_MAX inclusive by UBATCH_STEP. -# -# Usage: -# ./scripts/bench-ubatch-rocmi4.sh -# UBATCH_MIN=512 UBATCH_MAX=4096 UBATCH_STEP=512 CTX=8192 RUNS=3 \ -# ./scripts/bench-ubatch-rocmi4.sh -# -# Benchmarking policy: stop the production service first so the GPU is not -# starved. With STOPSVC=1 the script restarts frank-default-large on exit. -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -ENV_FILE="${FRANK_LARGE_ENV_FILE:-$SCRIPT_DIR/frank-default-large.env}" -if [[ -f "$ENV_FILE" ]]; then - set -a - # shellcheck disable=SC1090 - source "$ENV_FILE" - set +a -fi - -# --- sweep parameters ------------------------------------------------------- -: "${UBATCH_FIXED:=512}" -: "${BATCH_VALUES:=512 1024 1536 2048}" -: "${CTX:=16384}" # 16k context -: "${OUT_TOKENS:=256}" -: "${RUNS:=3}" # decode runs per ubatch -: "${PORT_BASE:=7894}" # scratch ports 7894.. -: "${STOPSVC:=1}" # stop/restart the live unit for a clean GPU - -# --- resolved paths (fall back to serve-script defaults) -------------------- -: "${LLAMA_BIN:=/home/dank/ciru-qwen38/ROCmFPX/build-promptforge/bin/llama-server}" -: "${MODEL_DIR:=/home/dank/models/kingjones-Qwen3.8-27B-STRIX-MTP}" -: "${MODEL_FILE:=Qwen3.8-27B-Uncensored-Q6_0_ROCMFPX_AGENT.gguf}" -: "${MTP_DRAFT_FILE:=$MODEL_DIR/mtp-Qwen3.8-27B-Q4_0.gguf}" -: "${MMPROJ_FILE:=mmproj-Qwen3.8-27B-Q8_0.gguf}" -: "${CHAT_TEMPLATE_FILE:=$SCRIPT_DIR/chat-template.jinja}" -: "${ALIAS:=frank-default-large}" -: "${HSA_OVERRIDE_GFX_VERSION:=11.5.1}" -: "${GGML_HIP_ENABLE_UNIFIED_MEMORY:=1}" -: "${MTP_N_MAX:=16}" -: "${MTP_P_MIN:=0.60}" - -MODEL="$MODEL_DIR/$MODEL_FILE" -MMPROJ="$MODEL_DIR/$MMPROJ_FILE" - -for f in "$MODEL" "$CHAT_TEMPLATE_FILE"; do - if [[ ! -f "$f" ]]; then - echo "missing required file: $f" >&2 - exit 1 - fi -done -if [[ ! -x "$LLAMA_BIN" ]]; then - echo "missing llama-server binary: $LLAMA_BIN" >&2 - exit 1 -fi - -MMPROJ_ARGS=() -if [[ -f "$MMPROJ" ]]; then - MMPROJ_ARGS=(--mmproj "$MMPROJ") -fi - -STopped=0 -SCRATCH_PID="" -cleanup() { - if [[ -n "$SCRATCH_PID" ]] && kill -0 "$SCRATCH_PID" 2>/dev/null; then - echo "=== stopping scratch llama-server (pid $SCRATCH_PID) ===" - kill "$SCRATCH_PID" 2>/dev/null || true - wait "$SCRATCH_PID" 2>/dev/null || true - SCRATCH_PID="" - fi - if [ "$STopped" = 1 ]; then - echo "=== restarting frank-default-large ===" - sudo systemctl restart frank-default-large || true - STopped=0 - fi -} -trap cleanup EXIT - -stop_live_service() { - if [ "$STOPSVC" = 1 ]; then - echo "=== stopping frank-default-large for a clean GPU ===" - sudo systemctl stop frank-default-large || true - STopped=1 - fi -} - -# wait_health — poll /health until ready or the server dies. -wait_health() { - local pid="$1" port="$2" i ready=0 crashed=0 - for i in $(seq 1 900); do - if ! kill -0 "$pid" 2>/dev/null; then crashed=1; break; fi - if curl -sf "http://127.0.0.1:${port}/health" >/dev/null 2>&1; then - ready=1; break - fi - [ $((i % 10)) = 0 ] && echo -n . - sleep 2 - done - [ "$crashed" = 1 ] && { echo " BACKEND_CRASHED (before ready)"; return 2; } - [ "$ready" = 1 ] || { echo " FAIL (timeout)"; return 1; } - echo " [ready]" - return 0 -} - -# run_one — boot one server instance, run -# the workload, parse print_timing, print a RESULT line. -run_one() { - local batch="$1" port="$2" out_tokens="$3" runs="$4" - local ubatch="$UBATCH_FIXED" - local log="/tmp/ubatch-${ubatch}-batch-${batch}-p${port}.log" - : > "$log" - - echo "=== ubatch=${ubatch} ctx=${CTX} batch=${batch} port=${port} runs=${runs} ===" - "$LLAMA_BIN" \ - --model "$MODEL" \ - "${MMPROJ_ARGS[@]}" \ - --chat-template-file "$CHAT_TEMPLATE_FILE" \ - --chat-template-kwargs '{"enable_thinking":true,"preserve_thinking":true,"preserve_reasoning":true}' \ - --alias bench-ubatch \ - --host 127.0.0.1 --port "$port" \ - -dev ROCm0 -ngl 999 -np 1 \ - -t 16 -tb 32 \ - -c "$CTX" -b "$batch" -ub "$ubatch" \ - -fa on -ctk f16 -ctv f16 -ctxcp 16 \ - --cache-ram 8192 --cache-prompt --metrics --no-mmap \ - --spec-type draft-mtp --model-draft "$MTP_DRAFT_FILE" \ - --spec-draft-device ROCm0 --spec-draft-ngl 999 \ - --spec-draft-type-k f16 --spec-draft-type-v f16 \ - --spec-draft-n-max "$MTP_N_MAX" --spec-draft-n-min 0 \ - --spec-draft-p-min "$MTP_P_MIN" \ - --spec-draft-backend-sampling \ - --temp 0.7 --top-p 0.95 \ - --reasoning "$REASONING" --reasoning-format "$REASONING_FORMAT" --reasoning-budget -1 \ - -lv 4 >>"$log" 2>&1 & - local pid=$! - SCRATCH_PID="$pid" - if ! wait_health "$pid" "$port"; then - tail -30 "$log" - kill "$pid" 2>/dev/null || true - wait "$pid" 2>/dev/null || true - SCRATCH_PID="" - return 2 - fi - - # one python pass per run; reuse the same boot across runs - local r - for r in $(seq 1 "$runs"); do - echo "--- run ${r}/${runs} ---" - if ! python3 - "$ubatch" "$port" "$out_tokens" "$CTX" "$log" "$r" <<'PY' -import sys, time, json, urllib.request -ubatch, port, out_tokens, ctx, log, run_number = sys.argv[1], sys.argv[2], int(sys.argv[3]), int(sys.argv[4]), sys.argv[5], sys.argv[6] - -# Flowing prose at ~75% of ctx so MTP acceptance is realistic. -VAR = ( - "narrative nonfiction blends observation with reflection, ", - "and a careful writer lets each clause earn its place ", - "in the sentence's rising line, so meaning accumulates slowly. ", - "The reader is not hurried; the reader is led. Loose and periodic, ", - "the style turns on concrete nouns and verbs that carry weight, ", - "and it resists the abstract noun that hides a vague claim. ", - "A paragraph is not a list. It is a single turn of thought that ", - "opens, develops, and lands. This is the craft of prose. Choose, ", - "arrange, and above all omit. Omission is the discipline that ", - "keeps the whole clear, and clarity is the courtesy we owe the ", - "willing reader." -) -n = len(VAR) -parts = [] -t = 0 -while t * 76 < (ctx * 3) // 4: - for k in range(5): - parts.append(VAR[(t + k) % n]) - parts.append(f" This is cued by note {t}. ") - t += 1 -prompt = "".join(parts) -prompt += ("\n\nNow, in a detailed and flowing paragraph, continue this essay about " - "the philosophy of language and its influence on modern computation. ") - -body = json.dumps({ - "prompt": prompt, "max_tokens": out_tokens, - "temperature": 0.0, "stream": False, -}).encode() -req = urllib.request.Request(f"http://127.0.0.1:{port}/v1/completions", - data=body, headers={"Content-Type": "application/json"}) -t0 = time.time() -try: - with urllib.request.urlopen(req, timeout=7200) as resp: - out = json.load(resp) -except Exception: - print("request failed; preserving benchmark cleanup", file=sys.stderr) - raise -dt = time.time() - t0 -pt = out.get("usage", {}).get("prompt_tokens", 0) -ct = out.get("usage", {}).get("completion_tokens", 0) - -lines = open(log, encoding="utf-8", errors="ignore").read().splitlines() -prefill = [l for l in lines if "prompt eval time" in l] -evals = [l for l in lines if "eval time" in l and "prompt eval" not in l] - -def grab(arr): - return arr[-1].strip() if arr else "n/a" - -g_tps = "" -if evals and ct: - # eval timing line: "eval time = .. ms / N tokens (... tokens per second)" - import re - match = re.search(r"eval time =\s*([0-9.]+) ms", evals[-1]) - if match: - ms = float(match.group(1)) - g_tps = f"{ct / (ms / 1000):.2f}" -print(f"== RESULT ubatch={ubatch} run={run_number} " - f"PREFILL={grab(prefill)} GEN_TPS={g_tps} " - f"pt={pt} ct={ct} wall={dt:.1f}s") -PY - then - kill "$pid" 2>/dev/null || true - wait "$pid" 2>/dev/null || true - SCRATCH_PID="" - return 2 - fi - done - kill "$pid" 2>/dev/null || true - wait "$pid" 2>/dev/null || true - SCRATCH_PID="" -} - -main() { - stop_live_service - local idx=0 ubatch port - printf "%-10s %-14s %-10s\n" "UBATCH" "PREFILL" "GEN_TPS" - for batch in $BATCH_VALUES; do - port=$((PORT_BASE + idx)) - run_one "$batch" "$port" "$OUT_TOKENS" "$RUNS" - idx=$((idx + 1)) - done - echo "=== sweep complete ===" -} - -main "$@" diff --git a/scripts/bench-ubatch.sh b/scripts/bench-ubatch.sh new file mode 100755 index 0000000..991a02c --- /dev/null +++ b/scripts/bench-ubatch.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +# Benchmark ubatch with llama-bench for frank-default-large. +# +# This is intentionally a ubatch-only benchmark. It uses one llama-bench +# process, a fixed 16K prompt workload, and a fixed batch size large enough to +# hold that workload. No server process, HTTP requests, or log scraping are used. +# +# Run on Dankclaw after stopping frank-default-large: +# sudo systemctl stop frank-default-large +# ./scripts/bench-ubatch.sh +# sudo systemctl start frank-default-large +set -euo pipefail + +: "${LLAMA_BENCH:=/home/dank/GitPackages/ROCmFPX/build-strix-rocmfp4/bin/llama-bench}" +: "${MODEL:=/home/dank/models/kingjones-Qwen3.8-27B-STRIX-MTP/Qwen3.8-27B-Uncensored-Q6_0_ROCMFPX_AGENT.gguf}" +: "${UBATCH_VALUES:=256,512,768,1024,1280,1536,1792,2048,3072,4096,5120,6144}" +: "${CONTEXT:=16384}" +: "${GEN_TOKENS:=128}" +: "${REPETITIONS:=3}" +: "${DEVICE:=ROCm0}" +: "${OUTPUT:=jsonl}" + +if [[ ! -x "$LLAMA_BENCH" ]]; then + printf 'missing executable llama-bench: %s\n' "$LLAMA_BENCH" >&2 + exit 1 +fi +if [[ ! -r "$MODEL" ]]; then + printf 'missing or unreadable model: %s\n' "$MODEL" >&2 + exit 1 +fi + +exec "$LLAMA_BENCH" \ + --model "$MODEL" \ + --n-prompt "$CONTEXT" \ + --n-gen "$GEN_TOKENS" \ + --batch-size "$CONTEXT" \ + --ubatch-size "$UBATCH_VALUES" \ + --device "$DEVICE" \ + --n-gpu-layers 999 \ + --cache-type-k f16 \ + --cache-type-v f16 \ + --flash-attn on \ + --mmap 0 \ + --repetitions "$REPETITIONS" \ + --output "$OUTPUT" diff --git a/tests/frank_large_config.bats b/tests/frank_large_config.bats index 5154010..69fc2a0 100644 --- a/tests/frank_large_config.bats +++ b/tests/frank_large_config.bats @@ -63,28 +63,16 @@ TEMPLATE_FILE="$REPO_ROOT/scripts/chat-template.jinja" grep -q -- '-ub "$UBATCH_SIZE"' "$SERVE_FILE" } -@test "large ubatch bench defaults to the active model and requested sweep" { - local bench="$REPO_ROOT/scripts/bench-ubatch-rocmi4.sh" - grep -q ': "${UBATCH_FIXED:=512}"' "$bench" - grep -q 'BATCH_VALUES.*512 1024 1536 2048' "$bench" - grep -q ': "${STOPSVC:=1}"' "$bench" - grep -q ': "${CTX:=16384}"' "$bench" - grep -q 'MODEL_FILE' "$bench" - grep -q 'REASONING_FORMAT' "$bench" -} - -@test "large ubatch bench cleans up scratch server on request failure" { - local bench="$REPO_ROOT/scripts/bench-ubatch-rocmi4.sh" - grep -q 'SCRATCH_PID=' "$bench" - grep -q 'kill.*SCRATCH_PID' "$bench" - grep -qF 'eval time =\s' "$bench" -} - -@test "large ubatch bench supports fixed ubatch batch sweep" { - local bench="$REPO_ROOT/scripts/bench-ubatch-rocmi4.sh" - grep -q ': "${UBATCH_FIXED:=512}"' "$bench" - grep -q 'BATCH_VALUES' "$bench" - grep -q -- '-b "$batch" -ub "$ubatch"' "$bench" +@test "llama-bench ubatch harness uses the active model" { + local bench="$REPO_ROOT/scripts/bench-ubatch.sh" + [ -x "$bench" ] + grep -q 'llama-bench' "$bench" + grep -q 'Qwen3\.8-27B-Uncensored-Q6_0_ROCMFPX_AGENT\.gguf' "$bench" + grep -q -- '--n-prompt "$CONTEXT"' "$bench" + grep -q -- '--ubatch-size "$UBATCH_VALUES"' "$bench" + grep -q -- '--batch-size "$CONTEXT"' "$bench" + grep -q -- '--mmap 0' "$bench" + ! grep -qE '(^|[[:space:]])llama-server|(^|[[:space:]])curl|print_timing' "$bench" } @test "frank-default-large uses the tracked Qwen-Sharp template on :7770" {