llama.cpp 本地大模型部署与使用文档
环境:Ubuntu 26.04(kernel 7.0.0-34)+ AMD Radeon RX 7900 XTX 24GB(gfx1100)+ Granite Ridge 核显
部署:llama.cpp b11227(Vulkan/RADV) + Qwen3.8-27B UD-Q4_K_M (16.46GB)
当前配置:上下文 131072(128k) ,KV q4_0,MTP 投机解码,显存 19.98GB (留 ~4GB 余量)
实测:prefill 387--781 tok/s ,decode 37.7--72 tok/s
prompt cache 已验证 :32k 文档重复请求 48.4s → 0.5s
服务:systemd 开机自启,监听
0.0.0.0:8080
0. 方案要点(为什么这样配)
| 配置 | 原因 |
|---|---|
| KV cache 用 q4_0 量化 | RDNA3没有原生 FP8 支持 ,KV 压缩只能用整数路径。本模型 64 层中 16 层是全注意力,fp16 KV 要 64 KiB/token (128k 需 8.6GB,加权重必然超 24GB);q4_0 只要 16.5 KiB/token(128k 仅 2.2GB) |
| 用 Vulkan 而非 ROCm/HIP | 官方提供预编译 Vulkan 二进制,免编译、免补丁、免 ROCm,开箱即用 |
-fa on 必开 |
Flash Attention 是长上下文 + KV 量化的前提,不开则 90k 以上上下文创建失败 |
| 开 MTP 投机解码 | GGUF 自带 BF16 MTP 头,decode 速度约翻倍(36 → 66+ tok/s),无需额外模型文件 |
--parallel 1 |
MTP 是单流优化,多并发会失去投机收益 |
GGML_VK_VISIBLE_DEVICES=1 |
本机有核显 + 独显两块 Vulkan 设备,必须锁定独显(见 §5.1) |
能力边界 :24GB 单卡上,128k 上下文占 20GB(留 4GB 余量);把 -c 调到 262144 可达 256k 窗口(实测吃到 248,872 token),但显存会吃满(24.1GB)且长上下文速度下降(见 §3.5)。
1. 环境前提
| 项目 | 本机 | 说明 |
|---|---|---|
| GPU | RX 7900 XTX 24GB(Vulkan1)+ 核显(Vulkan0) | 必须锁定独显(见 §5.1) |
| Vulkan 运行时 | libvulkan1 1.4.341 + mesa-vulkan-drivers 26.0.8(RADV) + vulkan-tools | 26.04 默认已装,无需额外安装 |
| 磁盘 | GGUF 16.46GB + 二进制 31MB | 当前可用 38GB |
| 内存占用 | 19.98GB @128k / 24.14GB @256k | 128k 配置留 ~4GB 余量 |
| 依赖 | 无 ------ 不需要 ROCm、不需要编译 | 预编译 Vulkan 版开箱即用 |
2. 安装步骤(实际执行过的)
2.1 确认 Vulkan 运行时
bash
ls /usr/share/vulkan/icd.d/ # 应看到 radeon_icd.json
dpkg -l | grep -E "libvulkan1|mesa-vulkan-drivers|vulkan-tools"
2.2 下载 llama.cpp 预编译 Vulkan 版
bash
mkdir -p /home/<用户名>/llm/llama-vk && cd /home/<用户名>/llm/llama-vk
# GitHub 直连在国内不通,走 gh-proxy 镜像
curl -fL --max-time 1800 -o llama-vulkan.tar.gz \
"https://gh-proxy.com/https://github.com/ggml-org/llama.cpp/releases/download/b11227/llama-b11227-bin-ubuntu-vulkan-x64.tar.gz"
tar xzf llama-vulkan.tar.gz # 得到 /home/<用户名>/llm/llama-vk/llama-b11227/
2.3 下载模型(GGUF)
选 unsloth/Qwen3.8-27B-GGUF 的 Qwen3.8-27B-UD-Q4_K_M.gguf (16,464,440,224 字节),
GGUF 里自带 BF16 MTP 头,投机解码无需额外文件。
bash
cat > /tmp/dl_gguf.py <<'EOF'
import os
os.environ["HF_ENDPOINT"] = "https://hf-mirror.com"
os.environ["HF_HUB_DISABLE_XET"] = "1" # 必须:Xet 协议镜像不支持(401)
from huggingface_hub import hf_hub_download
print(hf_hub_download("unsloth/Qwen3.8-27B-GGUF",
"Qwen3.8-27B-UD-Q4_K_M.gguf", local_dir=os.path.expanduser("~") + "/llm/models/gguf"))
EOF
python3 /tmp/dl_gguf.py
2.4 确认 GPU 设备编号
bash
cd /home/<用户名>/llm/llama-vk/llama-b11227
LD_LIBRARY_PATH=. ./llama-server --list-devices
本机输出:
Vulkan0: AMD Radeon Graphics ← 核显,别用
Vulkan1: Radeon RX 7900 XTX ← 独显,目标
2.5 安装 systemd 服务(当前生效的配置)
单元文件里必须写绝对路径 :把
<用户名>换成实际运行服务的 Linux 用户(例如/home/alice/...)。⚠️ systemd 单元不展开
~,所以这里一律写成/home/<用户名>/llm/...;也可以用 systemd 的%h代替家目录(如%h/llm/llama-vk/llama-b11227)。
bash
sudo tee /etc/systemd/system/llama-server.service > /dev/null <<'EOF'
[Unit]
Description=llama.cpp server (Qwen3.8-27B UD-Q4_K_M, 128k ctx, q4_0 KV, MTP spec-decode)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=<用户名>
WorkingDirectory=/home/<用户名>/llm/llama-vk/llama-b11227
SupplementaryGroups=video render
Environment=PATH=/usr/local/bin:/usr/bin:/bin
Environment=LD_LIBRARY_PATH=/home/<用户名>/llm/llama-vk/llama-b11227
Environment=GGML_VK_VISIBLE_DEVICES=1
ExecStart=/home/<用户名>/llm/llama-vk/llama-b11227/llama-server \
-m /home/<用户名>/llm/models/gguf/Qwen3.8-27B-UD-Q4_K_M.gguf \
-c 131072 -ngl 999 -fa on \
-ctk q4_0 -ctv q4_0 \
--spec-type draft-mtp --spec-draft-n-max 2 --parallel 1 \
--jinja \
--host 0.0.0.0 --port 8080
Restart=always
RestartSec=10
TimeoutStartSec=1800
StandardOutput=append:/home/<用户名>/llm/llama-server.log
StandardError=append:/home/<用户名>/llm/llama-server.log
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now llama-server.service
3. 使用
3.1 服务管理
bash
systemctl status llama-server # 状态
systemctl restart llama-server # 重启(改参数后)
systemctl stop llama-server # 停止
systemctl disable llama-server # 取消开机自启
tail -f /home/<用户名>/llm/llama-server.log # 看日志(含 prefill 进度、每条请求计时)
启动到可用约 15--30 秒(等待期间 health 返回 000/503 属正常)。
3.2 调用 API
服务同时提供 OpenAI 兼容接口和 llama.cpp 原生接口。
bash
# 对话(OpenAI 格式,客户端推荐用这个)
curl http://127.0.0.1:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "qwen3.8",
"messages": [{"role": "user", "content": "介绍一下 ROCm"}],
"max_tokens": 512,
"temperature": 0.7
}'
# 流式(打字机效果)
curl http://127.0.0.1:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"qwen3.8","messages":[{"role":"user","content":"讲个故事"}],
"max_tokens":800,"stream":true}'
# 原生接口(返回详细 timings,便于看速度)
curl http://127.0.0.1:8080/completion \
-H "Content-Type: application/json" \
-d '{"prompt":"写一首关于星空的短诗:","n_predict":256}'
# 运维接口
curl http://127.0.0.1:8080/health # 200 = 正常
curl http://127.0.0.1:8080/v1/models # 模型信息(含 n_ctx)
curl http://127.0.0.1:8080/props # 服务器完整配置
Python 客户端:
python
from openai import OpenAI
client = OpenAI(base_url="http://127.0.0.1:8080/v1", api_key="EMPTY")
r = client.chat.completions.create(
model="qwen3.8",
messages=[{"role": "user", "content": "你好"}],
max_tokens=300)
print(r.choices[0].message.content)
响应
usage.prompt_tokens_details.cached_tokens会告诉你这次复用了多少缓存 token。
3.3 访问方式
| 场景 | 地址 |
|---|---|
| 服务器本机 | http://127.0.0.1:8080 |
| 局域网其他设备 | http://<服务器IP>:8080(监听 0.0.0.0) |
3.4 关键启动参数
| 参数 | 值 | 含义 |
|---|---|---|
-m |
/home/<用户名>/llm/models/gguf/Qwen3.8-27B-UD-Q4_K_M.gguf |
模型文件 |
-c |
131072 |
上下文长度(128k)。可调到 262144(256k,已验证,但显存吃满) |
-ngl 999 |
--- | 全部层卸载到 GPU |
-fa on |
--- | Flash Attention(必须开,长上下文+KV 量化的前提) |
-ctk q4_0 / -ctv q4_0 |
--- | K/V cache 量化(关键:不量化则 90k 以上上下文创建失败) |
--spec-type draft-mtp |
--- | 启用模型自带 MTP 投机解码(decode 提速约 1 倍) |
--spec-draft-n-max 2 |
--- | 每轮草稿 token 数(同卡实测 2 最优) |
--parallel 1 |
--- | 单并发(MTP 最优设置,多并发会失去投机收益) |
GGML_VK_VISIBLE_DEVICES=1 |
环境变量 | 只暴露独显,避免误用核显 |
3.5 常用调整
bash
sudo nano /etc/systemd/system/llama-server.service # 改 -c / --spec-draft-n-max 等
sudo systemctl daemon-reload && sudo systemctl restart llama-server
上下文长度的取舍(都是实测值):
-c 值 |
最大输入 | 显存 | 满载冷 prefill | 满载 decode |
|---|---|---|---|---|
| 131072(当前) | 128k | 20.0 GB(留 4GB) | 5.4 min | 37.7 tok/s |
| 262144 | 256k(实测 248,872) | 24.1 GB(无余量) | 16.9 min | 25.6 tok/s |
从 128k 切到 256k 的命令:
bash
sudo sed -i 's/-c 131072/-c 262144/' /etc/systemd/system/llama-server.service
sudo systemctl daemon-reload && sudo systemctl restart llama-server
- 想要更高解码速度 :把
--spec-draft-n-max在 2--4 之间 sweep - 需要并发服务 :去掉
--parallel 1并提高数值,但 MTP 收益会消失
4. 性能实测(本机,2026-09-28)
4.1 长上下文吞吐(q4_0 KV,MTP 开)
| 实际输入 | prefill | decode | 备注 |
|---|---|---|---|
| 4k | 781 tok/s | 66.0 tok/s | |
| 34k | 675 tok/s | 55.4 tok/s | |
| 124k | 387 tok/s(321.6s) | 37.7 tok/s | 128k 配置的满载区间 |
| 155,548 | 342.5 tok/s(454.1s) | 35.7 tok/s | *需-c 262144 |
| 248,872(≈95% 窗口) | 245.8 tok/s(1012.6s) | 25.6 tok/s | *需-c 262144 |
- prefill 速度随上下文增长而下降(4k 781 → 250k 246 tok/s),这是注意力计算量增长的必然结果
- MTP 全程生效:日志
draft acceptance = 0.625, mean len = 2.25(250k 场景) - 短请求(~100 token 生成)实测 decode 可达 71.9 tok/s
- 显存:131k 19.98GB ,262k 24.14GB
4.2 prompt cache 实测(32,260 token 文档)
| 请求 | 耗时 | 复用 token | 实际 prefill |
|---|---|---|---|
| ① 首次(冷启动) | 48.4s | 0 | 32,260 token(695 tok/s) |
| ② 完全相同的请求 | 0.5s | 32,256 | 仅 4 token |
| ③ 同文档 + 新问题 | 1.5s | 31,744 | 仅新增 517 token |
结论 :一份长文档只需付一次 prefill;之后每轮提问 1--2 秒 出首字。
这是长上下文能实用的关键 ------ 例如 128k 文档首次 5.4 分钟,之后每次追加提问只要 1 秒级。
4.3 设备与显存读数注意
rocm-smi把 RADV(Vulkan) 的分配记在 GTT 而不是 VRAM(本机 VRAM 读数恒为 ~28MB,实际占用看 GTT)- 判断有没有用对显卡,看 decode 速度:>50 tok/s = 独显正常;<5 tok/s = 误用核显
5. 踩坑与排错
5.1 双 GPU 必须锁定独显(最容易踩)
本机有两块 Vulkan 设备(核显 Vulkan0 / 独显 Vulkan1)。两个过滤方式不能叠加:
ini
Environment=GGML_VK_VISIBLE_DEVICES=1 # 只保留独显 → 它变成 Vulkan0
ExecStart=... -dev Vulkan1 # ❌ 再指定 Vulkan1 就找不到了
→ 报错 error while handling argument "-dev": invalid device: Vulkan1 并无限重启。
正确做法(当前配置) :只用 GGML_VK_VISIBLE_DEVICES=1,不加 -dev。
5.2 常见现象对照
| 症状 | 原因 / 解决 |
|---|---|
| health 返回 000/503 | 模型还在加载,等 15--60 秒 |
服务反复重启,日志有invalid device |
设备过滤叠加了,见 §5.1 |
| 上下文创建失败(90k 以上) | 没加-ctk q4_0 -ctv q4_0,或没开 -fa on |
| decode 只有几 tok/s | 用了核显,或权重掉到系统内存(检查是否有其他程序占显存) |
| 生成速度比预期慢很多 | --parallel 设成了 >1,MTP 收益消失 |
| 长 prompt"卡住"很久 | 正常:看日志prompt processing, progress = 0.xx 有进度;也可 rocm-smi --showuse 确认 GPU 100% |
| 输入超上限报错 | 输入超过-c 设定值;调大 -c 或缩短输入 |