llm-setup.md 21.7 KB

本地大模型服务配置总览

最后更新:2026-08-18 机器角色:本地 GPU 服务器,运行 Qwen/DeepSeek 大模型,通过 FRP 反向代理对外提供服务 硬件:4 × RTX 2080 Ti 22G(Turing / compute capability 7.5)· 125G 内存 · Ubuntu 22.04 · 驱动 570.153.02 / CUDA 12.8


〇、当前运行状态(2026-08-18)

服务 端口 容器/进程 模型
大模型 API 8000 容器 llm38 Qwen3.8-27B-Q5_K_M(llama.cpp b10481)
语音转写 8002 容器 whisper whisper-server
文本嵌入 7590 gunicorn (PID 1616) M3E-base
文生图 7860(仅本机) venv (PID 1919) SD WebUI Forge

实测指标:生成 23.4 tok/s(150K 上下文时 16.7)· 显存 57.3 GB / 88 GB(四卡)· 单请求上下文 262144(256K)

容器名从 llm 改成了 llm38。旧的 llm 容器保留为 Exited (0) 状态作回滚锚点,不要删。


一、整体架构

外部用户
    |
    v
qwen.wudaima.com  (FRP 公网域名,无鉴权 ⚠)
    |
    v
FRP 服务端 60.205.214.169:7009
    |
    v
本机 127.0.0.1:8000
    |
    +-- [当前] llama.cpp b10481 (容器 llm38)
    |   +-- Qwen3.8-27B-Q5_K_M.gguf
    |
    +-- [历史] llama.cpp b9570 (容器 llm)  <- 回滚用,已停
    |   +-- Qwen3.6-27B-IQ4_XS.gguf
    |
    +-- [历史] vLLM 引擎 (compose.72b/32b.yaml)
        +-- Qwen2.5-72B-Instruct-GPTQ-Int4
        +-- DeepSeek-R1-Distill-Qwen-32B-AWQ

注意:8000 端口同一时间只能跑一个模型服务。


二、★ 这台机器的硬件约束(2080 Ti = Turing SM 7.5)★

这一节是 2026-08-18 部署 Qwen3.8 时踩出来的,新增模型前必读,能省掉大量试错。

2.1 绝对做不了的事

事项 原因
跑 FP8 量化模型 FP8 原生需要 Ada(8.9)/Hopper(9.0);vLLM 的 fp8_marlin 回退内核也要求 Ampere(8.0+)。SM 7.5 两条路都够不着,无法绕过
用 bf16 Turing 不支持。现有 vLLM compose 全都写 --dtype=half 就是这个原因
用 server-cuda13 镜像 CUDA 13 运行时要求驱动 580+,本机是 570.153.02,会报 forward compatibility was attempted on non supported HW
MTP 推测解码提速 --spec-type draft-mtp 在 sm_75 上对 hybrid(SSM+attention) 模型不激活(llama.cpp issue #24670)。网上「+33~39% 速度」的说法在本机吃不到
1M 上下文 需约 92.8 GB,超过物理 85.9 GB。注意 262144 是 n_ctx_train,超过它才需要 YaRN 外插——--ctx-size 524288 --parallel 2 是每槽 262144,属原生范围,不涉及 YaRN(旧版本此行写「524288 (YaRN 2x)」,把总池和单请求上限混淆了,已订正)

2.2 能做但有坑

事项 说明
llama.cpp build 下限 跑 Qwen3.8/qwen35 架构必须 ≥ b10450(DeltaNet CUDA 修复 commit ece963f4)。b10419~b10450 的症状是加载正常、显存正常、tok/s 正常、输出全是乱码 —— 极具迷惑性,务必先查 build 号
CUDA graphs PR #25749 给 Volta/Turing 开了 CUDA graphs,但 issue #25835 报告显存逐轮增长直至 OOM。本机部署已设 GGML_CUDA_DISABLE_GRAPHS=1 规避,上游修复后可 A/B 试着去掉
JIT 编译 SM 7.5 在官方镜像里只有 PTX 没有 SASS,冷启动要驱动现编 1~2 分钟。已挂 /data/nvcache38:/root/.nv 持久化缓存
prefill 偏慢 GDN chunked kernel 未合入(PR #26001),走逐 token 路径。不要拿 3090/4090 的 pp 数字做验收标准
KV cache 量化 Turing 上 cc --cache-type-k/v 会触发额外 f16 转换开销,收益常被吃掉。显存够就保持 f16
-sm 切分模式 四卡无 P2P(nvidia-smi topo -p2p r 全 CNS)且 GPU0/1 与 GPU2/3 跨 NUMA。必须用 -sm layer,row/tensor 会走主机内存性能灾难
默认 tensor-split 陷阱 llama.cpp 按总显存而非剩余显存均分。GPU0 有 embedding+SD 占约 840 MiB、GPU3 有 whisper 占 3258 MiB,起第二个大模型时必须显式 -ts 或 --device,否则 GPU3 先 OOM

2.3 能跑 Turing 的证据(供以后怀疑时复查)

  • ggml/src/ggml-cuda/CMakeLists.txt 中 list(APPEND CMAKE_CUDA_ARCHITECTURES 75-virtual 80-virtual 86-real) 在所有 CUDA 版本判断之外,无条件执行
  • .devops/cuda.Dockerfile 用 CUDA_DOCKER_ARCH=default,即走上面的默认列表
  • 容器日志 ARCHS = 500,610,700,750,800,860,890,900,1200,750 在内
  • Gated DeltaNet 算子 gated_delta_net.cu 全文无任何架构判断,纯 fp32 + warp reduce
  • Flash Attention 的 ggml_cuda_fattn_mma_get_config_turing() 前四条就是 CONFIG_CASE(256,256,...),正是 Qwen3.8 的 head_dim=256

三、★ 网络约束(下载模型/镜像前必读)★

目标 状态 替代
huggingface.co ❌ 不通(超时) ✅ hf-mirror.com
ghcr.io blob 层 ❌ 拉取空转重试 ✅ ghcr.nju.edu.cn(南大代理,拉完 docker tag 回原名)
Docker Hub ✅ 已配镜像加速 /etc/docker/daemon.json 中 registry-mirrors

出口带宽约 5.9 MB/s 封顶(实测:换域名并发也突破不了,是本机出口上限而非对端限速)。20GB 模型约需 60 分钟,并行下载不会更快,只会互相抢。下载务必用断点续传(curl -C -)+ setsid nohup 脱离 SSH 会话。


四、模型文件位置

所有模型在 /data/models/(1.8T 盘,当前用 1.4T):

模型 路径 说明
Qwen3.8-27B-GGUF /data/models/Qwen/Qwen3.8-27B-GGUF/ 当前在跑。Q5_K_M 19.32GiB + mmproj-f16 885MB
Qwen3.6-27B-GGUF /data/models/Qwen/Qwen3.6-27B-GGUF/ 回滚目标,IQ4_XS
Qwen3-Coder-Next-GGUF /data/models/Qwen/Qwen3-Coder-Next-GGUF/
Qwen3-30B-A3B-Instruct-2507 /data/models/Qwen/Qwen3-30B-A3B-Instruct-2507/
Qwen2.5-72B-Instruct-GPTQ-Int4 /data/models/Qwen/Qwen2.5-72B-Instruct-GPTQ-Int4/ vLLM 用
Qwen-72B-Chat-Int4 / QwQ-32B-Preview /data/models/Qwen/ 旧版
DeepSeek-R1-Distill-Qwen-32B(-AWQ) / 14B / 7B /data/models/deepseek-ai/、/data/models/DeepSeek-R1-Distill-Qwen-32B-AWQ/
GLM-4.7-Flash-GGUF / gpt-oss-120b-GGUF /data/models/unsloth/ 未部署
M3E 嵌入模型 /home/admin/llm/m3e-base/ 7590 端口在用

五、★ 当前部署:Qwen3.8-27B ★

不走 compose,用 docker run 脚本管理,脚本都在 /home/admin/qwen38-deploy/:

脚本 作用
cutover.sh 切换到 Qwen3.8(校验 sha256/字节 → 停 llm → 起 llm38 → 等健康)
restore.sh 一键回滚到 Qwen3.6,约 3-5 分钟
download.sh 断点续传下载模型(60 次重试)
pull2.sh 从 NJU 代理拉镜像并 retag

5.1 启动命令

docker run -d --name llm38 --restart unless-stopped --gpus all \
  -p 8000:8000 \
  -v /data/models:/jsxc \
  -v /data/nvcache38:/root/.nv \
  -e GGML_CUDA_DISABLE_GRAPHS=1 \
  -e CUDA_CACHE_MAXSIZE=2147483648 \
  --health-cmd "curl -fsS http://localhost:8000/health || exit 1" \
  --health-interval 30s --health-timeout 5s \
  --health-retries 3 --health-start-period 600s \
  llamacpp:b10481 \
  -m /jsxc/Qwen/Qwen3.8-27B-GGUF/Qwen3.8-27B-Q5_K_M.gguf \
  --alias qwen3.8-27b \
  --host 0.0.0.0 --port 8000 \
  -ngl 99 -sm layer -fa on \
  --ctx-size 524288 --no-context-shift \
  --batch-size 2048 --ubatch-size 512 \
  --parallel 2 --jinja --seed 3407 \
  --temp 1.0 --top-p 0.95 --top-k 20 --min-p 0.0 --presence-penalty 0.0

5.2 每个非显然参数的理由

参数 为什么
llamacpp:b10481 本地钉死的固定 tag(指向 5ebaf50acac4)。不用浮动的 server-cuda,避免以后被静默换成不兼容版本
GGML_CUDA_DISABLE_GRAPHS=1 规避 Turing CUDA graphs 显存泄漏(#25835)
-v /data/nvcache38:/root/.nv 持久化 JIT 缓存,省掉每次冷启 1~2 分钟
--health-cmd ...:8000/health 覆盖镜像内写死的 localhost:8080。旧 llm 容器常年 unhealthy(FailingStreak 16000+)就是这个原因,不覆盖会掩盖真故障
--health-start-period 600s 首次加载含 JIT + 20GB mmap,实测 82 秒,给足宽限
--alias qwen3.8-27b 给下游稳定的 model id,避免文件名变更打穿调用方
-ngl 99 该 build 的 -ngl 默认 auto、--fit 默认 on,不显式指定会为了塞下 ctx 把层甩到 CPU,速度崩塌且不报错
-sm layer 见 2.2 节,四卡无 P2P
-fa on Turing 有 head_dim=256 专用调优表,可放心开
--batch-size 2048 --ubatch-size 512 比 3.6 的 4096/1024 保守;ubatch 直接决定 compute buffer 大小
采样参数 Qwen3.8 thinking 模式官方推荐 temp 1.0 / top-p 0.95 / top-k 20 / min-p 0.0。llama.cpp 默认是 top_k=40 / min_p=0.05,不写就是错的;也不要照抄 3.6 的 0.7/40/0.01
不挂 --mmproj 首轮纯文本。mmproj 文件已下好在同目录,需要图像输入时再加
不用 --spec-type draft-mtp 见 2.1 节,sm_75 上不激活

5.3 显存实测(ctx=131072)

GPU0  7448 MiB   GPU1  7336 MiB   GPU2  8110 MiB   GPU3  6532 MiB
llm38 合计 29426 MiB / 88 GB

低于 25 GB 说明有层落到 CPU 了,回查 -ngl 与 --fit。

KV cache 之所以小:64 层里只有 16 层是标准注意力(full_attention_interval=4),48 层是 Gated DeltaNet,KV 只有同规格纯注意力模型的约 27%。所以还有很大上下文余量:262144 约需 42 GB,524288 约需 59 GB。

5.4 Thinking 模式与上下文语义(实测)

Thinking 默认开启,思考过程走独立的 reasoning_content 字段,不混进 content:

{"messages":[...]}                                              // 默认开启
{"messages":[...],"chat_template_kwargs":{"enable_thinking":false}}  // 关闭

模板另支持 reasoning_effort 调节思考深度。

★ --ctx-size 是总池不是单请求上限 ★

--ctx-size 给的是总池,被 --parallel 均分才是单请求上限。当前 --ctx-size 524288 --parallel 2 → 每槽 262144(日志 n_ctx_slot = 262144)。

模型原生上限是 262144,不是 131072。 权威来源是 GGUF 元数据本身,别信二手说法:

python3 /home/admin/qwen38-deploy/ggufmeta.py /data/models/Qwen/Qwen3.8-27B-GGUF/Qwen3.8-27B-Q5_K_M.gguf
# qwen35.context_length = 262144      ← 原生上限
# qwen35.block_count = 65             (64 真实层 + 1 个未用的 nextn/MTP 层)
# qwen35.full_attention_interval = 4  (64 层里只有 16 层是标准注意力)
# qwen35.attention.head_count_kv = 4 / key_length = 256 / value_length = 256

KV 用量可以手算:16 注意力层 × 4 KV 头 × 256 维 × 2(K+V)× 2 字节 = 64 KiB/token。乘上总池 token 数就是 KV 显存,实测与估算吻合到 1 GB 以内。

配置 单请求上限 并发 KV 实测/估算总显存
--parallel 4 --ctx-size 131072(旧) 32K 4 8 GiB 29.4 GB(实测)
--parallel 2 --ctx-size 524288(当前) 256K 2 32 GiB 57.3 GB(实测)
--parallel 1 --ctx-size 262144 256K 1 16 GiB ~38 GB
--parallel 4 --ctx-size 1048576 256K 4 64 GiB ~86 GB ❌ 放不下
同上 + -ctk q8_0 -ctv q8_0 256K 4 32 GiB ~55 GB(未验证)

长上下文的真实成本(实测,别只看显存放不放得下):

提示长度 提示处理速度 光 prefill 耗时
11K 1491 tok/s 7.4 s
150K 979 tok/s 153 s
256K(外推) ~950 tok/s ~4.5 分钟

prefill 期间独占一个槽位(共 2 个)。生成速度也随上下文下降:短上下文 23.4 tok/s,150K 时 16.7 tok/s。

切换脚本:/home/admin/qwen38-deploy/ctx256k.sh,自带失败自动回滚。


5.5 如何调用(对外 API)

OpenAI 兼容。model id 是 qwen3.8-27b(--alias 固定,不随权重文件名变)。

入口 地址 适用
公网 http://qwen.wudaima.com/v1 外网/办公网,经 FRP,仅 HTTP
内网 http://192.168.110.67:8000/v1 同局域网,直连更快
本机 http://localhost:8000/v1 SSH 进服务器后

无鉴权——API key 随便填或不填都通(见第七节安全告警)。

curl http://qwen.wudaima.com/v1/chat/completions -H "Content-Type: application/json" -d '{
  "model": "qwen3.8-27b",
  "messages": [{"role":"user","content":"你好"}],
  "max_tokens": 1024
}'
from openai import OpenAI
client = OpenAI(base_url="http://qwen.wudaima.com/v1", api_key="none")  # key 必填但不校验
r = client.chat.completions.create(model="qwen3.8-27b",
        messages=[{"role":"user","content":"你好"}], max_tokens=1024)
print(r.choices[0].message.content)

四个调用方必须知道的点:

  1. thinking 内容在 reasoning_content 字段,不在 content。max_tokens 是两者之和——给小了会导致思考没结束就被截断,content 返回空字符串。要正常输出,max_tokens 给足(≥512),或用 "chat_template_kwargs":{"enable_thinking":false} 关掉思考。
  2. 采样参数别自己传。服务端已按 Qwen3.8 官方 thinking 推荐值启动(temp 1.0 / top-p 0.95 / top-k 20 / min-p 0.0),请求里再传会覆盖掉,容易传成 llama.cpp 的默认值(top_k 40 / min_p 0.05)而降质。
  3. model 字段不校验。传错名字也返回 200 + 正常内容,所以别指望用它做健康检查——用 /health 或 /v1/models。
  4. 长提示很贵。256K 提示光 prefill 约 4.5 分钟且独占 1/2 槽位,客户端超时要放宽(timeout=900)。

常用端点:/health(存活)、/v1/models(模型 + n_ctx/n_ctx_train)、/props(total_slots、build 号)、/v1/chat/completions、/v1/completions。


六、★ 回滚 ★

bash /home/admin/qwen38-deploy/restore.sh

会清掉 llm38、用 llamacpp-rollback:b9570 镜像重建 llm 跑回 Qwen3.6-27B-IQ4_XS,约 3-5 分钟。

回滚依赖三样东西,都不要删:

  1. 镜像 tag llamacpp-rollback:b9570(image id 9dad14fcc497)
  2. 模型文件 /data/models/Qwen/Qwen3.6-27B-GGUF/Qwen_Qwen3.6-27B-IQ4_XS.gguf
  3. 脚本 /home/admin/qwen38-deploy/restore.sh

FRP/nginx 无需改动 —— 域名始终指向 127.0.0.1:8000。


七、模型切换操作(vLLM 历史配置)

⚠ /home/admin/jiansuan/ 下的 vLLM compose 镜像是 v0.8.4 / v0.10.2(11~16 个月前),不认识 Qwen3.5+ 的新架构,只能跑 Qwen2.5 / DeepSeek 这些老模型。

7.1 配置文件目录

/home/admin/jiansuan/
├── vllm/
│   ├── compose.72b.yaml          # Qwen2.5-72B (vLLM)
│   ├── compose.32b.yaml          # DeepSeek-32B (vLLM)
│   ├── compose.coder.30b.yaml    # Qwen3-Coder-30B (vLLM)
│   ├── compose.7b.1m.yaml        # 7B 超长上下文 (vLLM, 只用 GPU2/3)
│   └── compose.yaml              # 基础模板(sleep,不自动启动)
└── llama_cpp/
    └── compose-qwen3-coder-next.yaml

所有 compose 都用 container_name: llm + ports: 8000:8000,同一时间只能跑一个。切换前必须先 docker stop llm38。

7.2 各 vLLM 配置要点

配置 模型 关键参数
compose.72b.yaml Qwen2.5-72B-GPTQ-Int4 --dtype=half -tp 4 --gpu-memory-utilization 0.98 --max-model-len 32768
compose.32b.yaml DeepSeek-R1-Distill-32B-AWQ --enable-reasoning --reasoning-parser deepseek_r1 -tp 4 --max-model-len 65536
compose.coder.30b.yaml Qwen3-Coder-30B-A3B --enable-auto-tool-choice --tool-call-parser hermes -tp 4 --max-model-len 131072
compose.7b.1m.yaml Qwen2.5-7B-1M --max-model-len 614400 --kv-cache-dtype fp8 -tp 2,只用 GPU2/3

镜像统一为 registry.cn-hangzhou.aliyuncs.com/jiansuan/vllm-openai:v0.8.4,挂载 /data/models:/root/models。

7.3 切换示例

cd /home/admin/jiansuan
docker stop llm38                                   # 先停当前的 Qwen3.8
docker compose -f vllm/compose.72b.yaml up -d
curl http://localhost:8000/v1/models                # 预期 qwen2.5-72b

八、反向代理配置

8.1 FRP 内网穿透

目录:/home/admin/llm/frp/ · 服务端:60.205.214.169:7009 · 由 PM2 管理(God Daemon PID 1467)

配置文件 section 域名 本地端口 状态
frpc.qwen.ini [qwen-llm] qwen.wudaima.com 8000 ✅ 大模型 API
frpc.qwen.ini [qwen-gw-20260610] gateway.wudaima.com 8080 ⚠ 8080 无监听,隧道空挂
frpc.ini [m3e] m3e.embedding.wudaima.com 7590 ✅ 嵌入服务
sd.frpc.ini [stable-diffusion-web] stable-diffusion.wudaima.com 7860 ✅ SD WebUI
comfyui.frpc.ini [comfyui-web] comfyui.wudaima.com 8188 ⚠ 8188 无监听,隧道空挂
ssh.frpc.ini [ssh] — (tcp) 22 → 公网 6000 ⚠ SSH 暴露公网
pm2 list                          # 需先 export PATH 或用绝对路径
pm2 logs frpc-qwen-client --lines 50

⚠ 安全:qwen.wudaima.com 无任何鉴权,公网可直接调用本机 27B 推理。gateway.wudaima.com → 8080 那条隧道看名字本是做网关鉴权用的,但 8080 一直没有服务。SSH 映射到公网 6000 且允许密码登录、admin 在 sudo 组。

8.2 Nginx

配置目录:/etc/nginx/conf.d/

文件 监听 静态根 后端
farm.conf :4016 /home/admin/chat/chatbot-front/dist 127.0.0.1:4017 + 127.0.0.1:4020
manage.conf :4018 /home/admin/chat/manage-front/dist 127.0.0.1:4019 + 127.0.0.1:4020

⚠ 已知故障:4017 / 4019 / 4020 三个后端端口全部无监听。两个前端静态页能打开,但任何 API 请求都会 502。聊天应用目前是坏的。

8.3 AutoSSH

/etc/systemd/system/autossh.service —— 状态 active + enabled,把本机 22 反向代理到 AWS:

autossh -N -M 0 -R 10044:localhost:22 ubuntu@2404:c2c0:8522:c100:f7b:8b85:5fec:e174

九、LLM Farm 应用栈

目录:/data/models/llmfarm/ · Compose:docker-compose.yml

服务:chatbot-frontend(:10000) · chatbot-backend · chatbot-mysql(:3306, 密码 Chain.123456) · chatbot-chain · chatbot-chroma(:8000 仅容器内网) · chatbot-management-frontend(:9999) · chatbot-management-backend

Chroma 也用 8000,但只在 Docker 内部网络;大模型容器绑的是 0.0.0.0:8000,不冲突。


十、常用命令速查

# ---- 状态 ----
nvidia-smi
docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}"
docker logs llm38 --tail 100 -f
curl -s http://localhost:8000/v1/models | python3 -m json.tool
curl -s http://localhost:8000/health

# ---- 对话测试(model 字段可任意,服务端不校验)----
cat > /tmp/t.json <<'EOF'
{"messages":[{"role":"user","content":"你好"}],"max_tokens":200}
EOF
curl -s http://localhost:8000/v1/chat/completions -H "Content-Type: application/json" -d @/tmp/t.json

# ---- 新模型上线前必查(血泪教训)----
docker run --rm --entrypoint /app/llama-server <镜像> --version    # build 必须 >= 10450
docker run --rm --entrypoint /app/llama-server <镜像> --help | grep -- '--某参数'  # 新版对未知参数是 throw 不是忽略
sha256sum /data/models/.../xxx.gguf                                 # 与 HF 官方值比对

# ---- 显存核对 ----
nvidia-smi --query-gpu=index,memory.used,memory.free --format=csv
nvidia-smi --query-compute-apps=pid,used_memory --format=csv

# ---- 回滚 ----
bash /home/admin/qwen38-deploy/restore.sh

# ---- 外部验证 ----
curl -s http://qwen.wudaima.com/v1/models | python3 -m json.tool

十一、目录速查表

用途 路径
模型文件 /data/models/
Qwen3.8 部署脚本 /home/admin/qwen38-deploy/
JIT 缓存 /data/nvcache38/
vLLM / llama.cpp Compose /home/admin/jiansuan/vllm/、/home/admin/jiansuan/llama_cpp/
FRP 客户端 /home/admin/llm/frp/
Nginx 配置 /etc/nginx/conf.d/
AutoSSH 服务 /etc/systemd/system/autossh.service
LLM Farm Compose /data/models/llmfarm/docker-compose.yml
本备忘文档 /home/admin/llm-setup.md

十二、注意事项

  1. 上新模型前先读第二、三节。FP8 装不了、build 有下限、HF 连不上 —— 这三条能省掉大半天。

  2. unhealthy 不再是"正常现象"。旧文档说 llama.cpp 容器常显示 unhealthy 不影响使用 —— 那是因为镜像内 healthcheck 写死了 localhost:8080 而服务在 8000。llm38 已用 --health-cmd 覆盖,现在 healthy 是真的健康、unhealthy 是真的有问题。

  3. FRP 域名固定:qwen.wudaima.com 始终指向 127.0.0.1:8000,切换模型后域名不变,但 /v1/models 返回的 model id 会变。服务端不校验请求里的 model 字段,下游硬编码旧模型名也不会报错。

  4. 磁盘:/ 只剩 29G(87%),docker 镜像和日志都写这里,留意别写满。/data 还有 353G。

  5. GPU 占用基线(起大模型前先看):GPU0 有 embedding(668M)+SD(174M),GPU3 有 whisper(3258M)。这些常驻进程会让默认 tensor-split 算错,见 2.2 节。

  6. 不要同时起两个 27B。四卡总量够,但计算争用会让对外服务吞吐腰斩,且 GPU3 余量不足会 OOM。要并行验证请用 --device CUDA0,CUDA1,CUDA2 避开 GPU3 并把 ctx 砍到 32768。