<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Deepseek-Harness 单卡（7900XTX）运行Qwen3.8-27B 完整文档-1]]></title><description><![CDATA[<blockquote>
<p dir="auto">将本地 llama.cpp 推理服务接入 DeepSeek Harness (DSH) 的全过程：环境准备、服务部署、shim 开发、DSH 配置、性能测试、日常运维与根因诊断。</p>
<p dir="auto">本文档合并自《DSH 接入本地模型完整教程》与《本地模型接入 DSH —— 诊断与解决方案》。</p>
<p dir="auto"><strong>实测环境</strong>：Ubuntu 22.04 · Intel CPU · 62GB RAM · AMD 7900 XTX (Vulkan, 25.75GB VRAM) · llama.cpp b11223 (Vulkan + MTP + ngram-k4v) · Node.js 24 · Qwen3.8-27B / Qwen3.6-27B (Q4_K_M)</p>
<p dir="auto"><strong>实测日期</strong>：2026-09-29（含同日 shim A/B 复核与 k4v 投机解码实测）</p>
</blockquote>
<hr />
<h2>目录</h2>
<ol>
<li><a href="#1-%E6%9E%B6%E6%9E%84%E6%A6%82%E8%A7%88">架构概览</a></li>
<li><a href="#2-%E7%8E%AF%E5%A2%83%E5%87%86%E5%A4%87">环境准备</a></li>
<li><a href="#3-%E9%83%A8%E7%BD%B2-llamacpp-%E6%8E%A8%E7%90%86%E6%9C%8D%E5%8A%A1">部署 llama.cpp 推理服务</a></li>
<li><a href="#4-%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8-shim">开发工具调用 Shim</a></li>
<li><a href="#5-%E9%85%8D%E7%BD%AE-dsh-%E6%8E%A5%E5%85%A5">配置 DSH 接入</a></li>
<li><a href="#6-%E6%80%A7%E8%83%BD%E6%B5%8B%E8%AF%95">性能测试</a></li>
<li><a href="#7-%E6%97%A5%E5%B8%B8%E8%BF%90%E7%BB%B4">日常运维</a></li>
<li><a href="#8-%E6%95%85%E9%9A%9C%E6%8E%92%E6%9F%A5">故障排查</a></li>
<li><a href="#9-%E8%AF%8A%E6%96%AD%E4%B8%8E%E5%AE%9E%E6%88%98%E8%AE%B0%E5%BD%95">诊断与实战记录</a></li>
<li><a href="#10-%E9%99%84%E5%BD%95%E6%96%87%E4%BB%B6%E6%B8%85%E5%8D%95">附录：文件清单</a></li>
</ol>
<blockquote>
<p dir="auto"><strong>本次更新（2026-09-30）</strong></p>
<ul>
<li>§9.8（重写）— <strong>CTX 从 256K 降到 128K 的根因</strong>：<code>vkAllocateMemory</code> 装不下 DEVICE_LOCAL，<br />
Vulkan 把权重退回 GTT，decode 掉到 0.49 tok/s。<code>llama.sh</code> 加 <code>-cram -1</code>。<br />
下界 64K（Hermes Agent 硬性要求 context &gt;= 64000），上界 128K（实测 ~21.2GB/25.75GB 留余量）。</li>
<li>§1.1 / §3.1 / §3.2 / §5.1 / §9.4 — 3.8 的 ctx 全面改为 131072；overlay 的<br />
<code>contextWindow: 131072</code> / <code>maxTokens: 32768</code>（原先声明 262144，是超限的直接来源）。</li>
<li>§3.1 内嵌代码块与 <code>~/llama-bin/llama.sh</code> 逐行同步（含 systemd 自动检测、<code>_USER_OVERRIDES</code> 机制）；<br />
§5.1 与 <code>~/llama-bin/dsh-shim-overlay.yml</code> 同步。</li>
<li>修正 §9.7 的锚点死链（<code>#97-补充复核shim-ab-与多轮稳定性</code>）。</li>
</ul>
<p dir="auto"><strong>上次更新（2026-09-29）</strong></p>
<ul>
<li>§1.2 / §9.1 / §9.7 — <strong>shim 必要性复核</strong>：直连 <code>:8080</code> 现可正常完成工具调用，<br />
原「直连 0/10」复现不出来；<code>coerce()</code> 在 18 次强制采样中零次触发。结论改为<br />
「shim 仍默认启用，但不应再被当作不可替代的必需品」。</li>
<li>§6.3（新增）— <strong>投机解码 k4v 实测</strong>：<code>code_refactor</code> <strong>+30.7%</strong>、合成重复 <strong>+42.3%</strong>，<br />
其余负载零退步。已默认启用，<code>size-n</code> 必须 32。</li>
<li>§3.1 / §5.1 — 内嵌代码块已与 <code>~/llama-bin/</code> 下真实文件重新同步。</li>
<li>§5.1 / §9.2 — overlay 默认模型由 3.6 改为 3.8。</li>
</ul>
</blockquote>
<hr />
<h2>1. 架构概览</h2>
<h3>1.1 三层架构</h3>
<pre><code class="language-bash">
DSH (Agent 框架)
  │  OpenAI 兼容请求 (含 tools 定义)
  ▼
dsh-llm-shim (:8090, Node.js)
  │  tool_choice 改为 none，转发纯文本流
  ▼
llama.cpp llama-server (Vulkan + MTP)
  :8080  Qwen3.8-27B  (ctx=131072)
  :8081  Qwen3.6-27B  (ctx=196608)  ← 两实例互斥
</code></pre>
<h3>1.2 为什么需要 Shim</h3>
<p dir="auto">llama.cpp b11223 早期实测中自带的 tool-call 解析器（peg-native）<strong>解析不了 Qwen3.x 的 XML 工具调用</strong>：第二个参数起的整段被吞进第一个参数，并诱发模型无限重复 tool_call 标签直到 max_tokens（实测 32768 token / 7.7 分钟，零可用输出）。</p>
<p dir="auto">shim 的策略：</p>
<ol>
<li>把 tool_choice 强制改为 none，llama.cpp 不做工具语法约束</li>
<li>模型原始 XML 完整出现在 message.content 里</li>
<li>shim 自行把 XML 解析成标准 OpenAI tool_calls</li>
<li>检测到重复 tool_call 时立即掐断上游流</li>
</ol>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>方案</th>
<th>成功率</th>
<th>单次耗时</th>
</tr>
</thead>
<tbody>
<tr>
<td>直连 llama.cpp（2026-09-29 首次）</td>
<td>0/10</td>
<td>~30s（跑飞）</td>
</tr>
<tr>
<td>经 shim（同期）</td>
<td><strong>10/10</strong></td>
<td><strong>~1s</strong></td>
</tr>
</tbody>
</table>
<blockquote>
<h3><img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/26a0.png?v=4368ee61982" class="not-responsive emoji emoji-android emoji--warning" style="height:23px;width:auto;vertical-align:middle" title="⚠" alt="⚠" /> 2026-09-29 复核：直连已可用，shim 的必要性需重新评估</h3>
<p dir="auto">同日按当前配置重新做 A/B，<strong>「直连 0/10」复现不出来</strong>：</p>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>场景</th>
<th>结果</th>
</tr>
</thead>
<tbody>
<tr>
<td>简单工具直连 <code>:8080</code></td>
<td><strong>10/10</strong>，无跑飞</td>
</tr>
<tr>
<td>DSH 真实复杂 schema（多工具）直连</td>
<td><strong>2/2</strong></td>
</tr>
<tr>
<td>上述 + thinking 开启</td>
<td><strong>2/2</strong></td>
</tr>
<tr>
<td><code>dsh headless</code> 端到端（仅换 baseURL 端口）</td>
<td><strong>66 文件 / <code>ubuntu-ai</code></strong>，与经 shim 完全一致</td>
</tr>
<tr>
<td>参数类型强制压测（7 字段全 required，含 number/boolean/array/object）</td>
<td>直连 <strong>12/12</strong> 类型全对、0 次字符串化；经 shim 6/6</td>
</tr>
</tbody>
</table>
<p dir="auto">原结论只有三种可能：① build 或依赖已变；② 模型行为随采样/上下文漂移；③ 原基准本身有问题。<br />
本轮未能定位到确切原因。<strong>因此 shim 仍默认启用，但不应再被当作不可替代的必需品。</strong></p>
<p dir="auto">另见 <a href="#97-%E8%A1%A5%E5%85%85%E5%A4%8D%E6%A0%B8shim-ab-%E4%B8%8E%E5%A4%9A%E8%BD%AE%E7%A8%B3%E5%AE%9A%E6%80%A7">§9.7</a> 对 <code>coerce()</code> 类型强转分支的专项测试。<br />
下线前的判断标准：切直连后连续跑真实 dsh 会话，观察是否出现工具参数校验失败。</p>
</blockquote>
<hr />
<h2>2. 环境准备</h2>
<h3>2.1 硬件要求</h3>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>组件</th>
<th>最低要求</th>
<th>实测配置</th>
</tr>
</thead>
<tbody>
<tr>
<td>CPU</td>
<td>8 核 x86_64</td>
<td>Intel 多核</td>
</tr>
<tr>
<td>RAM</td>
<td>32 GB</td>
<td>62 GB</td>
</tr>
<tr>
<td>GPU</td>
<td>Vulkan 1.3</td>
<td>AMD 25.75 GB VRAM</td>
</tr>
<tr>
<td>磁盘</td>
<td>30 GB 可用</td>
<td>NVMe SSD</td>
</tr>
</tbody>
</table>
<blockquote>
<p dir="auto">显存估算：Qwen3.8-27B Q4_K_M 文本约 23.72 GB，加视觉 mmproj 约 24.9 GB。两个 27B 模型不能同时运行。</p>
</blockquote>
<h3>2.2 软件依赖</h3>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>依赖</th>
<th>版本</th>
<th>用途</th>
</tr>
</thead>
<tbody>
<tr>
<td>Node.js</td>
<td>&gt;= 18</td>
<td>运行 shim（无第三方依赖）</td>
</tr>
<tr>
<td>llama.cpp</td>
<td>b11223 Vulkan</td>
<td>推理引擎</td>
</tr>
<tr>
<td>Python</td>
<td>&gt;= 3.8</td>
<td>基准测试</td>
</tr>
<tr>
<td>Vulkan 驱动</td>
<td>AMDGPU-Pro / Mesa</td>
<td>GPU 加速</td>
</tr>
</tbody>
</table>
<h3>2.3 模型文件</h3>
<pre><code class="language-bash">
ls ~/models/
# Qwen3.8-27B-UD-Q4_K_M.gguf    (16.4GB)
# Qwen3.6-27B-Q4_K_M-mtp.gguf
# mmproj-model-bf16.gguf         (视觉投影，可选)
</code></pre>
<hr />
<h2>3. 部署 llama.cpp 推理服务</h2>
<h3>3.1 管理脚本 <a href="http://llama.sh" rel="nofollow ugc">llama.sh</a></h3>
<p dir="auto">存放到 <code>~/llama-bin/llama.sh</code>：</p>
<pre><code class="language-bash">
#!/bin/bash
# 本地 LLM 服务统一入口 — llama.cpp Vulkan + MTP
#
#   ./llama.sh 38            启动 Qwen3.8-27B (:8080)（默认走 setsid 手动；systemd 托管时请用 systemctl --user start qwen38）
#   ./llama.sh 36            启动 Qwen3.6-27B (:8081)（顺带拉起 :8090 shim）
#   ./llama.sh stop          停止全部（38 自动走 systemctl --user stop qwen38，不会被自动拉回）
#   ./llama.sh status        查看状态
#   ./llama.sh 36 --use      停掉当前，换成 3.6
#   ./llama.sh 38 --vision   带视觉启动（加载期开关，需重启）
#   ./llama.sh 38 --fg       前台运行（给 systemd 用）
#
# 覆盖: CTX=196608 NMAX=2 UB=512 K4V=0 K4V_N=32 API_KEY=xxx MMPROJ=path LOG=path ./llama.sh 38
# 注意: 下面初始化 CTX/NMAX 前必须先抓取环境值，否则 CTX="" 会让 ${CTX:-默认} 失效
ENV_CTX="${CTX:-}"; ENV_NMAX="${NMAX:-}"; ENV_MMPROJ="${MMPROJ:-}"; ENV_EXTRA="${EXTRA:-}"
set -uo pipefail

BIN_DIR="${BIN_DIR:-$HOME/llama-bin/vulkan/llama-b11223}"
BIN="$BIN_DIR/llama-server"
SHIM="${SHIM:-$HOME/llama-bin/shim.sh}"   # 工具调用中间层（DSH 本地模型用）
VRAM_DEV=/sys/class/drm/card0/device

# 密钥：独立文件，不进脚本、不进 shell 环境
ENV_FILE="${ENV_FILE:-$HOME/.config/qwen38/api.env}"
[ -r "$ENV_FILE" ] &amp;&amp; { set -a; . "$ENV_FILE"; set +a; }

# profile: 别名|模型文件|ctx|nmax
# 均为 qwen35 混合架构: 65 层但 full_attention_interval=4 → 仅 16 层真注意力,
# 其余为 SSM 状态(常数大小, 与 ctx 无关). 训练上限均为 262144.
#
# 2026-09-30: CTX 从 262144 降到 131072。原注释说"多分配的 KV 不花钱"是错的——
# 24GB 卡上 256K 会把模型+KV 顶到 ~26.3GB/24.5GB，vkAllocateMemory 装不下
# DEVICE_LOCAL，Vulkan 后端把全部权重退回 GTT(系统内存)映射：实测
# vis_vram_used=567MiB / gtt_used=23490MiB，decode 掉到 0.49 tok/s、prefill 30 t/s。
# 「只有实际填满多少影响 decode」也不成立——溢出到 GTT 后每个 token 都要走 PCIe。
#
# 下界 64K：Hermes Agent 硬性要求 context &gt;= 64000，低于此直接拒绝连接。
# 上界实测：32K→17.4GB，128K→~21.2GB，256K→~26.3GB(爆)。128K 是留余量的最大值。
# 需要更大窗口用 ENV_CTX 覆盖，但别超 ~160K。
P38_ALIAS="qwen3.8-27b"; P38_MODEL="Qwen3.8-27B-UD-Q4_K_M.gguf";   P38_CTX=131072; P38_NMAX=3
P36_ALIAS="qwen3.6-27b"; P36_MODEL="Qwen3.6-27B-Q4_K_M-mtp.gguf"; P36_CTX=196608; P36_NMAX=3
P38_PORT="${P38_PORT:-8080}"
P36_PORT="${P36_PORT:-8081}"
MMPROJ_FULL="$HOME/models/mmproj-model-bf16.gguf"   # 仅 3.8 有效

# 记下「用户自己设的」调优变量。必须在这里取：下面第 45 行起会把 CTX/NMAX 清空
# 并给 UB/HOST/LOG/K4V 赋默认值，等到启动路径上再判断就分不清是用户设的还是默认了。
_TUNABLE="CTX ENV_CTX UB B NMAX ENV_NMAX K4V K4V_N K4V_M K4V_HITS CRAM EXTRA ENV_EXTRA TEMP TOPK CKV CV LOG HOST MMPROJ"
_USER_OVERRIDES=""
for _v in $_TUNABLE; do [ -n "${!_v-}" ] &amp;&amp; _USER_OVERRIDES="$_USER_OVERRIDES $_v"; done

KEY=""; ALIAS=""; MODEL=""; CTX=""; NMAX=""; PORT=""; NAME=""
case "${1:-}" in
    38|qwen38) KEY=38; NAME="Qwen3.8-27B"; ALIAS=$P38_ALIAS; MODEL=$P38_MODEL; CTX="${ENV_CTX:-$P38_CTX}"; NMAX="${ENV_NMAX:-$P38_NMAX}"; PORT=$P38_PORT ;;
    36|qwen36) KEY=36; NAME="Qwen3.6-27B"; ALIAS=$P36_ALIAS; MODEL=$P36_MODEL; CTX="${ENV_CTX:-$P36_CTX}"; NMAX="${ENV_NMAX:-$P36_NMAX}"; PORT=$P36_PORT ;;
esac

MODEL_PATH=""; [ -n "$MODEL" ] &amp;&amp; MODEL_PATH="$HOME/models/$MODEL"
API_KEY="${API_KEY:-${QWEN_API_KEY:-}}"
HOST="${HOST:-0.0.0.0}"

# 只匹配本模型的进程；用 alias 定位，避免 pkill -f "llama-server -m"
# 连带杀掉另一个模型的实例
_running() { pgrep -f -- "--alias $1" 2&gt;/dev/null; }
# 有 systemd 用户服务托管的模型（38 → qwen38.service），stop 必须走 systemctl：
# 否则 pkill 造成的"非正常退出"会被 Restart=on-failure 在 20s 后拉回来
_svc_of() { case "$1" in "$P38_ALIAS") echo qwen38.service ;; esac; }
# 注意 kind 必须是 shell 变量；写成 awk 的 -v 变量时 shell 展开不到
_vram() { local kind="$1"; awk '{printf "%.2f", $1/1e9}' "$VRAM_DEV/mem_info_vram_$kind" 2&gt;/dev/null; }

stop() {
    local n=0 a svc
    for a in "$P38_ALIAS" "$P36_ALIAS"; do
        if _running "$a" &gt;/dev/null; then
            svc=$(_svc_of "$a")
            if [ -n "$svc" ] &amp;&amp; systemctl --user is-active --quiet "$svc" 2&gt;/dev/null; then
                # systemd 接管中：受控停止，避免被 Restart 拉回；等进程真正退出
                systemctl --user stop "$svc" 2&gt;/dev/null
                for _ in $(seq 1 45); do _running "$a" &gt;/dev/null || break; sleep 1; done
            else
                pkill -f -- "--alias $a" 2&gt;/dev/null
                for _ in $(seq 1 45); do _running "$a" &gt;/dev/null || break; sleep 1; done
                pkill -9 -f -- "--alias $a" 2&gt;/dev/null
            fi
            n=$((n+1))
        fi
    done
    [ "$n" -gt 0 ] &amp;&amp; echo "已停止 $n 个实例" || echo "未在运行"
    # 模型都停了，工具调用中间层留着也没用；交给 shim.sh 用 PID 精确停，
    # 避免裸 pgrep/pkill 文件名误杀其它提到该文件的进程。
    if [ -x "$SHIM" ]; then
        _out=$("$SHIM" stop 2&gt;&amp;1)
        case "$_out" in *已停止*) echo "$_out" ;; esac
    fi
}

status() {
    printf '%-10s %-7s %-6s %-11s %s\n' 模型 端口 状态 显存 模型文件
    for spec in "Qwen3.8-27B|$P38_ALIAS|$P38_PORT|$P38_MODEL" "Qwen3.6-27B|$P36_ALIAS|$P36_PORT|$P36_MODEL"; do
        IFS='|' read -r name a p m &lt;&lt;&lt;"$spec"
        if _running "$a" &gt;/dev/null; then
            printf '%-10s %-7s %-6s %-11s %s\n' "$name" ":$p" "运行" "$(_vram used) GB" "$m"
        elif curl -fsS --max-time 2 "http://127.0.0.1:$p/health" &gt;/dev/null 2&gt;&amp;1; then
            # 进程看不见但端口在服务（例如从别的命名空间/终端起来的）也算运行
            printf '%-10s %-7s %-6s %-11s %s\n' "$name" ":$p" "运行" "-" "$m"
        else
            printf '%-10s %-7s %-6s %-11s %s\n' "$name" ":$p" "停止" "-" "$m"
        fi
    done
    # 以端口健康为准：shim 可能由别的终端/会话拉起，进程看不到但确实在服务
    if curl -fsS --max-time 2 "http://127.0.0.1:8090/health" &gt;/dev/null 2&gt;&amp;1; then
        _shim_state=运行; else _shim_state=停止; fi
    printf '%-10s %-7s %-6s %-11s %s\n' "shim" ":8090" "$_shim_state" "-" "dsh-llm-shim.mjs"
    echo "总显存: $(_vram used) / $(_vram total) GB"
}

if [ -z "$KEY" ]; then
    # 裸命令（函数已定义，可安全调用）：stop/status，其余按 status 兜底
    case "${1:-}" in
        stop) stop; exit 0 ;;
        status) status; exit 0 ;;
        *) status; exit 0 ;;
    esac
fi

MMPROJ=""
for a in "$@"; do
    case "$a" in
        --vision) MMPROJ="$MMPROJ_FULL" ;;
        --stop) stop; exit 0 ;;
        --status) status; exit 0 ;;
        --restart|--use)
            for a2 in "$@"; do
                case "$a2" in 36|qwen36) NEXT=36 ;; esac
            done
            stop; sleep 2
            case "${NEXT:-}" in 36) exec "$0" 36 --vision ;; *) exec "$0" 38 ;; esac ;;
    esac
done

[ -f "$MODEL_PATH" ] || { echo "模型缺失: $MODEL_PATH" &gt;&amp;2; exit 1; }
[ -x "$BIN" ] || { echo "二进制缺失: $BIN" &gt;&amp;2; exit 1; }

if _running "$ALIAS" &gt;/dev/null; then
    echo "$NAME 已在 :$PORT 运行。要重启先 stop，或用 ./llama.sh $KEY --use" &gt;&amp;2
    exit 1
fi
# 另一个模型占着显存就停掉，否则必然 OOM
if _running "$P38_ALIAS" &gt;/dev/null || _running "$P36_ALIAS" &gt;/dev/null; then
    echo "提示: 另一模型正在运行，将先停止（两者不能共存于 25.75GB 显存）"
    stop; sleep 2
fi
[ -n "$MMPROJ" ] &amp;&amp; [ ! -f "$MMPROJ" ] &amp;&amp; { echo "mmproj 缺失，关闭视觉: $MMPROJ" &gt;&amp;2; MMPROJ=""; }
# UB 默认 512：llama-bench 上 pp32768 有 399→414 t/s(+3.7%) 的提升，
# 但服务端实测 decode 63.7→59.3 t/s(−7%)、显存 23.64→24.83 GB(96%)，
# 且触发 "failed to fit params to free device memory"（-ngl 999 写死无法自动收缩）。
# prefill 的收益抵不上 decode 的损失，故保持 512。保留变量以便复测。
UB="${UB:-512}"; B="${B:-$((UB * 4))}"
# 实测: ub2048 纯文本 @256K 投影 22924 MiB, 视觉 mmproj 再 +1.19GB → 约 24.6GB / 25.75GB 极限
if [ -n "$MMPROJ" ] &amp;&amp; [ "$CTX" -gt 196608 ] &amp;&amp; [ "$UB" -gt 512 ]; then
    echo "⚠ ctx=$CTX + 视觉 + ub=$UB 会逼近 25.75GB 上限；建议 CTX=196608 或 UB=512" &gt;&amp;2
fi

# 投机解码: MTP 自投机 + n-gram 查表叠加(k4v)。
# 3.8 实测(temp=0, 每项 2 轮取中位数, n_gen 两配置完全一致故无输出长度混淆):
#   工作负载              draft-mtp   +k4v(n=32)     变化
#   code_refactor             86.1        112.6     +30.7%  ← dsh 改文件典型负载
#   repeat_synth              86.0        122.4     +42.3%
#   template_batch            47.2         46.7      -1.1%  (噪音内)
#   reasoning                 72.9         72.1      -1.1%  (噪音内)
#   prose                     47.2         46.7      -1.1%  (噪音内)
# 零 VRAM 成本, 零重编。n=32 是关键: n=12 在「格式重复但内容要换」的任务上倒扣
# 5~10%(n-gram 一直开火猜错时间轴), n=32 把所有负项收进 ±1.3% 噪音内。
# 原理: k4v 从已生成上下文做 n-gram 查表, 命中一次吐最多 48 token; 没命中退回 MTP。
# 来源: https://lcz.me/topic/1398 §二/§三(该文在 7900XTX Vulkan 上交叉验证过)
K4V="${K4V:-1}"
SPEC_TYPE="draft-mtp"; K4V_ARGS=()
if [ "$K4V" = "1" ]; then
    SPEC_TYPE="ngram-map-k4v,draft-mtp"
    K4V_ARGS=( --spec-ngram-map-k4v-size-n "${K4V_N:-32}"
               --spec-ngram-map-k4v-size-m "${K4V_M:-48}"
               --spec-ngram-map-k4v-min-hits "${K4V_HITS:-1}" )
fi

ARGS=( -m "$MODEL_PATH" -c "$CTX" -ngl 999 -fa 1
       -ub "$UB" -b "$B" --reasoning off --parallel 1
       -cram "${CRAM:--1}"
       --host "$HOST" --port "$PORT" --alias "$ALIAS"
       --spec-type "$SPEC_TYPE" --spec-draft-n-max "$NMAX" "${K4V_ARGS[@]}" )
# KV 精度与采样：草稿与目标共享 context，V cache 量化误差会压低接受率。
# 3.6 实测(reps=3, ctx196608, n-max4): 0.7/20 → 67.33 tok/s 接受0.544
#                                      0.3/5  → 68.70 tok/s 接受0.560
# 3.6 最佳: n-max3 + 0.3/5 → 69.54 tok/s 接受0.657 (V=q4_0 最优; q8_0/f16 反而更差更慢)
# 3.8 保持 0.7/20 —— 其 83.30 tok/s 是在该采样下实测的
if [ "$KEY" = "36" ]; then
    ARGS+=( -ctk "${CKV:-q8_0}" -ctv "${CV:-q4_0}"
            --temp "${TEMP:-0.3}" --top-k "${TOPK:-5}" )
else
    ARGS+=( -ctk "${CKV:-q4_0}" -ctv "${CV:-q4_0}"
            --temp "${TEMP:-0.7}" --top-k "${TOPK:-20}" )
fi
# EXTRA 透传调优参数，例如 EXTRA="--spec-draft-p-min 0.2"
[ -n "$ENV_EXTRA" ] &amp;&amp; ARGS+=( $ENV_EXTRA )
[ -n "$MMPROJ" ] &amp;&amp; ARGS+=(--mmproj "$MMPROJ")
# 关闭服务端认证: 配合 opencode 里 apiKey="" 使用，不传 --api-key
LOG="${LOG:-/tmp/qwen${KEY}-vulkan.log}"

echo "model  : $NAME  ($MODEL)"
echo "config : ctx=$CTX  n-max=$NMAX  ub=$UB/b=$B  视觉=$([ -n "$MMPROJ" ] &amp;&amp; echo 开 || echo 关)  认证=无"
echo "listen : $HOST:$PORT   log: $LOG"

for a in "$@"; do
    if [ "$a" = "--fg" ]; then
        # systemd 用的就是 --fg：exec 之后脚本就结束了，所以 shim 必须在这里起，
        # 否则「起模型就自动带 shim」在 --fg 下不成立。start 是幂等的。
        [ -x "$SHIM" ] &amp;&amp; "$SHIM" start
        exec "$BIN" "${ARGS[@]}"
    fi
done

# 有 systemd 单元托管时一律交给 systemctl：手动 setsid 起的进程不受
# Restart=on-failure 保护，还会占住端口让单元以 exit-code 1 反复 crash-loop
# （2026-09-30 22:10 就是这么把 qwen38 顶掉的）。
# systemctl 不继承当前 shell 的环境，所以存在调优变量或 --vision 时退回直起，
# 否则 `CTX=163840 ./llama.sh 38` 会被静默吞掉。
VIA=direct
_SVC=$(_svc_of "$ALIAS")
if [ -n "$_SVC" ] &amp;&amp; systemctl --user cat "$_SVC" &gt;/dev/null 2&gt;&amp;1; then
    if [ -n "$_USER_OVERRIDES" ] || [ -n "$MMPROJ" ]; then
        echo "⚠ 有覆盖[${_USER_OVERRIDES:- --vision}] systemd 不继承 → 直起（无 Restart 保护）" &gt;&amp;2
    else
        VIA=systemd
    fi
fi

PROBE=127.0.0.1; [ "$HOST" = "0.0.0.0" ] || PROBE="$HOST"
_fail_hint() { [ "$VIA" = systemd ] &amp;&amp; echo "journalctl --user -u $_SVC -n 50" || echo "$LOG"; }

if [ "$VIA" = systemd ]; then
    echo "→ systemctl --user start $_SVC"
    systemctl --user start "$_SVC"
else
    setsid nohup "$BIN" "${ARGS[@]}" &lt;/dev/null &gt;"$LOG" 2&gt;&amp;1 &amp; disown
fi

printf 'loading'
for _ in $(seq 1 90); do
    sleep 4
    curl -fsS --max-time 3 "http://$PROBE:$PORT/health" 2&gt;/dev/null | grep -q '"ok"' &amp;&amp; break
    _running "$ALIAS" &gt;/dev/null || { echo; echo "启动失败，看: $(_fail_hint)" &gt;&amp;2; exit 1; }
    printf '.'
done

if curl -fsS --max-time 3 "http://$PROBE:$PORT/health" 2&gt;/dev/null | grep -q '"ok"'; then
    echo; echo "就绪"
    awk -v u="$(_vram used)" -v t="$(_vram total)" \
        'BEGIN{printf "显存  : %s / %s GB (%.0f%%)\n", u, t, t?100*u/t:0}'
    LAN=$(ip route get 1.1.1.1 2&gt;/dev/null | grep -oE 'src [0-9.]+' | cut -d' ' -f2)
    [ -n "$LAN" ] &amp;&amp; echo "局域网: http://$LAN:$PORT/v1   (模型名 $ALIAS)"
    [ "$VIA" = direct ] &amp;&amp; { grep -q "nextn.*ignoring" "$LOG" 2&gt;/dev/null &amp;&amp; \
        echo "⚠ 日志显示 MTP 头被忽略，检查 --spec-type 是否生效" &gt;&amp;2; }
    # 工具调用中间层：llama.cpp b11223 自带的 tool-call 解析器解析不了 Qwen3.x 的 XML
    # 工具调用（参数被吞 + 诱发无限重复），改由 shim 代为解析。详见
    # ~/桌面/本地模型接入DSH-诊断.md。shim 已在跑时 start 是幂等的。
    if [ -x "$SHIM" ]; then
        "$SHIM" start
    else
        echo "⚠ 缺少 $SHIM，DSH 本地模型会拿不到工具调用" &gt;&amp;2
    fi
else
    echo; echo "超时，看 $(_fail_hint)" &gt;&amp;2; exit 1
fi
</code></pre>
<h3>3.2 使用说明</h3>
<pre><code class="language-bash">cd ~/llama-bin

./llama.sh 38          # 启动 Qwen3.8-27B (:8080)
./llama.sh 36          # 启动 Qwen3.6-27B (:8081)
./llama.sh status      # 查看状态
./llama.sh stop        # 停止所有
./llama.sh 36 --use   # 切换到 3.6（先停当前再起 3.6）
./llama.sh 38 --vision # 带视觉启动
./llama.sh 38 --fg     # 前台运行（systemd 用）
</code></pre>
<p dir="auto">关键参数说明：</p>
<ul>
<li><code>-ngl 999 -fa 1</code>：全部层 offload 到 GPU</li>
<li><code>-c 131072</code>：<strong>默认 ctx 128K，不是训练上限 262144</strong>。256K 会把模型+KV 顶到 ~26.3GB/25.75GB，<br />
Vulkan 装不下 DEVICE_LOCAL 而把权重退回 GTT（系统内存）映射，decode 掉到 0.49 tok/s。<br />
下界 64K（Hermes Agent 硬性要求 context &gt;= 64000）。<br />
需要更大窗口用 <code>CTX=65536 ./llama.sh 38</code> 覆盖，但每次加大都要重新核对显存（详见 §9.8）</li>
<li><code>-cram -1</code>：KV cache 放内存而非显存。25.75GB 卡上省下的正是权重能留在 DEVICE_LOCAL 的那点余量</li>
<li><code>--spec-type ngram-map-k4v,draft-mtp --spec-draft-n-max 3</code>：投机解码。<strong><code>ngram-map-k4v</code> 不可省略</strong>，详见 <a href="#63-%E6%8A%95%E6%9C%BA%E8%A7%A3%E7%A0%81%E4%B8%93%E9%A1%B9k4v">§6.3</a></li>
<li><code>--spec-ngram-map-k4v-size-n 32</code>：n-gram 查表长度，<strong>必须 32</strong>。取 12 会在「格式重复但内容要换」的任务上倒扣 5~10%</li>
<li><code>-ctk q4_0 -ctv q4_0</code>：KV cache 量化（只换容量不换速度）</li>
<li><code>--parallel 1</code>：单并发（本地推理推荐）</li>
<li><code>--reasoning off</code>：关闭推理模式（Qwen3.x）</li>
<li><strong>systemd 自动检测</strong>：有 <code>qwen38.service</code> 单元时，无覆盖变量的启动自动走 <code>systemctl --user start</code>；<br />
有 <code>CTX</code>/<code>UB</code>/<code>K4V</code> 等覆盖或 <code>--vision</code> 时退回直起（systemd 不继承 shell 环境），并打印警告</li>
</ul>
<blockquote>
<p dir="auto"><strong>常见误用</strong>：<code>./llama.sh use 36</code> 不是有效命令。切换模型用 <code>./llama.sh 36 --use</code>，<br />
或直接 <code>./llama.sh stop &amp;&amp; ./llama.sh 36</code>。在 32GB 以外的显存预算下两个 27B 无法共存，<br />
脚本会自动先停另一个。</p>
</blockquote>
<hr />
]]></description><link>https://lcz.me/topic/2013</link><generator>RSS for Node</generator><lastBuildDate>Fri, 02 Oct 2026 14:10:52 GMT</lastBuildDate><atom:link href="https://lcz.me/topic/2013.rss" rel="self" type="application/rss+xml"/><pubDate>Wed, 30 Sep 2026 16:00:06 GMT</pubDate><ttl>60</ttl></channel></rss>