feat: frank-default-large → llama.cpp Qwen3.6-27B Q8_K_P + DFlash draft + ngram-mod (bench pending)

This commit is contained in:
Frank
2026-08-11 13:47:37 -07:00
parent a74c1c5bcb
commit aa2f4bfe39
5 changed files with 268 additions and 4 deletions
@@ -75,6 +75,16 @@ vllm serve <target-model> \
llama.cpp supports DFlash, DSpark, and MTP through `--spec-type`. When you pass `--spec-draft-hf`, llama.cpp auto-discovers the sidecar files and infers the type. The priority order is DSpark, DFlash, EAGLE-3, MTP.
`--spec-type` accepts a **comma-separated list** of speculative implementations. `common_speculative_init` (in `common/speculative.cpp`) builds a vector of implementations and chains drafting through all of them (`common_speculative_draft_params.drafting` is "used to chain the drafts through all the available implementations"). This means you can **layer ngram-mod on top of a DFlash draft model** in one server process:
```
llama-server --hf-repo <target-GGUF> \
--spec-draft-hf <dflash-draft-GGUF> \
--spec-type draft-dflash,ngram-mod
```
The DFlash drafter proposes, then ngram-mod runs as an additional implementation in the chain. Verified against upstream `common/speculative.cpp` (b10133+).
DFlash on llama.cpp:
```
+150
View File
@@ -0,0 +1,150 @@
#!/usr/bin/env bash
# bench-frank-large.sh — 64k-context benchmark for the frank-default-large stack.
#
# Measures generation throughput of Qwen3.6-27B Q8_K_P + DFlash + ngram-mod on
# a single backend at 64k context. It starts llama-server on a scratch port,
# drives one generation through the OpenAI-compatible API with a ~64k-token
# prompt, and prints tokens/sec.
#
# Usage:
# ./scripts/bench-frank-large.sh <ROCm0|Vulkan0> [--generations N]
#
# Example:
# ./scripts/bench-frank-large.sh ROCm0
# ./scripts/bench-frank-large.sh Vulkan0 --generations 3
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DEVICE="${1:?usage: bench-frank-large.sh <ROCm0|Vulkan0> [--generations N]}"
GENERATIONS=1
if [ "${2:-}" = "--generations" ]; then
GENERATIONS="${3:?--generations needs a count}"
fi
MODEL_DIR=/home/dank/models
MODEL_FILE=Qwen3.6-27B-Uncensored-HauhauCS-Aggressive-Q8_K_P.gguf
DRAFT_FILE=Qwen3.6-27B-DFlash-Q8_0.gguf
LLAMA_SERVER=/home/dank/llama.cpp/build/bin/llama-server
MODEL="$MODEL_DIR/$MODEL_FILE"
DRAFT="$MODEL_DIR/$DRAFT_FILE"
PORT=7890
CTX=65536
PARALLEL=1
BATCH=2048
UBATCH=512
OUT_TOKENS=256
for f in "$MODEL" "$DRAFT"; do
if [[ ! -f "$f" ]]; then
echo "missing required file: $f" >&2
exit 1
fi
done
echo "=== 64k benchmark: device=$DEVICE model=$MODEL_FILE ==="
# --- start the server ----------------------------------------------------
"$LLAMA_SERVER" \
--model "$MODEL" \
--spec-draft-model "$DRAFT" \
--spec-type "draft-dflash,ngram-mod" \
--spec-draft-n-max 8 \
--spec-ngram-mod-n-min 48 \
--spec-ngram-mod-n-max 64 \
--spec-ngram-mod-n-match 24 \
--device "$DEVICE" \
--gpu-layers all \
--cache-type-k q8_0 \
--cache-type-v f16 \
--ctx-size "$CTX" \
--parallel "$PARALLEL" \
--batch-size "$BATCH" \
--ubatch-size "$UBATCH" \
--alias bench \
--host 127.0.0.1 \
--port "$PORT" \
> "/tmp/frankbench-$DEVICE.log" 2>&1 &
SERVER_PID=$!
echo "server pid $SERVER_PID on 127.0.0.1:$PORT"
trap 'kill $SERVER_PID 2>/dev/null; wait $SERVER_PID 2>/dev/null' EXIT
# --- wait for ready ------------------------------------------------------
echo -n "waiting for server"
ready=0
for i in $(seq 1 400); do
if curl -sf "http://127.0.0.1:$PORT/health" >/dev/null 2>&1; then
echo " [ready]"
ready=1
break
fi
echo -n "."
sleep 2
done
if [ "$ready" != "1" ]; then
echo ""
echo "FAIL: server did not become ready"
tail -25 "/tmp/frankbench-$DEVICE.log"
exit 1
fi
# --- drive N generations and measure --------------------------------------
python3 - "$DEVICE" "$CTX" "$OUT_TOKENS" "$GENERATIONS" "$PORT" <<'PY'
import sys, time, json, urllib.request, statistics
device = sys.argv[1]
ctx = int(sys.argv[2])
out_tokens = int(sys.argv[3])
generations = int(sys.argv[4])
port = sys.argv[5]
# ~64k-token prompt. One prose unit is ~110 chars ≈ 28 tokens for this model.
# We aim for a prompt of ~60k tokens so generation stays within the 64k ctx.
unit = ("The careful study of natural language reveals patterns of meaning "
"that emerge from the deliberate arrangement of words and phrases. ")
# approximate: unit is 160 chars, ~40 tokens; repeat to ~1500 units = ~60k tokens
prompt = unit * 1500
def req(prompt, n):
body = json.dumps({
"prompt": prompt,
"max_tokens": n,
"temperature": 0.0,
"stream": False,
}).encode()
r = urllib.request.Request(f"http://127.0.0.1:{port}/v1/completions",
data=body, headers={"Content-Type": "application/json"})
with urllib.request.urlopen(r, timeout=1800) as resp:
return json.load(resp)
# prefill timing
t0 = time.time()
first = req(prompt, 0)
t_pre = time.time() - t0
used = first.get("usage", {})
prompt_tokens = used.get("prompt_tokens", 0)
print(f"prefill: {prompt_tokens} tokens in {t_pre:.2f}s "
f"({prompt_tokens/t_pre:.1f} tok/s)")
# generation timing
rates = []
total_gentok = 0
total_time = 0.0
for i in range(generations):
t0 = time.time()
out = req(prompt, out_tokens)
dt = time.time() - t0
uit = out.get("usage", {})
comp = uit.get("completion_tokens", 0)
gt = dt - t_pre # approximate generation time = total - prefill
r = comp / gt if gt > 0 else 0
rates.append(r)
total_gentok += comp
total_time += gt
print(f"gen {i+1}: {comp} tokens in {gt:.2f}s ({r:.1f} tok/s)")
avg = statistics.mean(rates) if rates else 0
print(f"\nRESULT device={device} prompt_tokens={prompt_tokens} "
f"avg_gen_tok_s={avg:.1f} over {generations} runs")
PY
+31
View File
@@ -0,0 +1,31 @@
# Tunables for frank-default-large (HauhauCS Qwen3.6-27B Q8_K_P, DFlash+ngram-mod, dankclaw)
# Runtime: llama.cpp llama-server; AMD Strix Halo (gfx1151) 96GB UMA; Ryzen AI MAX+ 395.
MODEL_DIR=/home/dank/models
ALIAS=frank-default-large
HOST=0.0.0.0
PORT=7770
# 6 parallel slots x 65536 ctx each = 393216 total context pool.
CTX_SIZE=393216
PARALLEL=6
# Spec: DFlash draft model layered with ngram-mod. --spec-type accepts a
# comma-separated list; llama.cpp chains the implementations (verified in
# common/speculative.cpp). --spec-draft-n-max must stay below the DFlash
# block size (BF16 sidecar = 8 token block), so 8 is the ceiling.
SPEC_TYPE=draft-dflash,ngram-mod
SPEC_DRAFT_N_MAX=8
SPEC_NGRAM_MOD_N_MIN=48
SPEC_NGRAM_MOD_N_MAX=64
SPEC_NGRAM_MOD_N_MATCH=24
# Backend device for offload, from --list-devices. Pick the faster of:
# ROCm0 (HIP / ROCm backend)
# Vulkan0 (RADV Vulkan backend)
DEVICE=ROCm0
# Dense model (Qwen3.6-27B, not MoE): no MoE CPU offload. Full offload.
N_GPU_LAYERS=all
# KV on GPU, quantized (K q8_0, V f16); mirrors the small service profile.
CACHE_TYPE_K=q8_0
CACHE_TYPE_V=f16
BATCH_SIZE=2048
UBATCH_SIZE=512
THREADS=8
THREADS_HTTP=8
+3 -4
View File
@@ -1,16 +1,15 @@
[Unit]
Description=vLLM Ling-3.0-flash INT4 (CIRU gfx1151) -- frank-default-large
Description=llama.cpp Qwen3.6-27B Q8_K_P DFlash+ngram-mod (frank-default-large)
After=network.target
[Service]
Type=simple
User=dank
Group=dank
WorkingDirectory=/home/dank/ling3-ciru/Ling-3.0-Flash-CIRU-int4-Strix-native
Environment="HOST=0.0.0.0"
Environment="PORT=7770"
Environment="SERVED_MODEL_NAME=frank-default-large"
ExecStart=/home/dank/vllmScripts/scripts/serve-ling3-vllm.sh
Environment="FRANK_LARGE_ENV_FILE=/home/dank/vllmScripts/scripts/frank-default-large.env"
ExecStart=/home/dank/vllmScripts/scripts/serve-llama-frank-large.sh
Restart=on-failure
RestartSec=10
+74
View File
@@ -0,0 +1,74 @@
#!/usr/bin/env bash
# Serve HauhauCS Qwen3.6-27B-Uncensored-HauhauCS-Aggressive:Q8_K_P
# with DFlash draft + ngram-mod speculative decoding on dankclaw
# (AMD Strix Halo gfx1151, ROCm or Vulkan backend).
# Runtime: llama.cpp llama-server.
#
# Tunables come from frank-default-large.env in this directory unless the
# FRANK_LARGE_ENV_FILE environment variable overrides that path.
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
# --- defaults (used when the env file does not set them) -----------------
: "${MODEL_DIR:=/home/dank/models}"
: "${MODEL_FILE:=Qwen3.6-27B-Uncensored-HauhauCS-Aggressive-Q8_K_P.gguf}"
: "${DRAFT_FILE:=Qwen3.6-27B-DFlash-Q8_0.gguf}"
: "${ALIAS:=frank-default-large}"
: "${HOST:=0.0.0.0}"
: "${PORT:=7770}"
: "${CTX_SIZE:=393216}"
: "${PARALLEL:=6}"
: "${SPEC_TYPE:=draft-dflash,ngram-mod}"
: "${SPEC_DRAFT_N_MAX:=8}"
: "${SPEC_NGRAM_MOD_N_MIN:=48}"
: "${SPEC_NGRAM_MOD_N_MAX:=64}"
: "${SPEC_NGRAM_MOD_N_MATCH:=24}"
: "${DEVICE:=ROCm0}"
: "${N_GPU_LAYERS:=all}"
: "${CACHE_TYPE_K:=q8_0}"
: "${CACHE_TYPE_V:=f16}"
: "${BATCH_SIZE:=2048}"
: "${UBATCH_SIZE:=512}"
: "${THREADS:=8}"
: "${THREADS_HTTP:=8}"
LLAMA_SERVER="${LLAMA_SERVER:-/home/dank/llama.cpp/build/bin/llama-server}"
MODEL="$MODEL_DIR/$MODEL_FILE"
DRAFT="$MODEL_DIR/$DRAFT_FILE"
for f in "$MODEL" "$DRAFT"; do
if [[ ! -f "$f" ]]; then
echo "missing required file: $f" >&2
exit 1
fi
done
exec "$LLAMA_SERVER" \
--model "$MODEL" \
--spec-draft-model "$DRAFT" \
--spec-type "$SPEC_TYPE" \
--spec-draft-n-max "$SPEC_DRAFT_N_MAX" \
--spec-ngram-mod-n-min "$SPEC_NGRAM_MOD_N_MIN" \
--spec-ngram-mod-n-max "$SPEC_NGRAM_MOD_N_MAX" \
--spec-ngram-mod-n-match "$SPEC_NGRAM_MOD_N_MATCH" \
--device "$DEVICE" \
--gpu-layers "$N_GPU_LAYERS" \
--cache-type-k "$CACHE_TYPE_K" \
--cache-type-v "$CACHE_TYPE_V" \
--ctx-size "$CTX_SIZE" \
--parallel "$PARALLEL" \
--batch-size "$BATCH_SIZE" \
--ubatch-size "$UBATCH_SIZE" \
--threads "$THREADS" \
--threads-http "$THREADS_HTTP" \
--alias "$ALIAS" \
--host "$HOST" \
--port "$PORT"