【部署教程】双 2080 Ti 22GB 跑动 27B!从零到 agent 可用的完整路径,避坑指南【qwen3.8 27B本地部署#01】

一句话内容简介:一套 2018 年的双 2080 Ti(22GB×2),如何从零搭建 vLLM 环境、在「官方 + 自己调」的多个方案里找到最适配配置、跑通 138K 长上下文的 Qwen3.8-27B 并接入 agent 长期运行——含全部命令、参数与踩坑。特别说明:138K 是本机实测的上限,不是官方给的值。


一、为什么这个部署值得写

先说实话:RTX 2080 Ti 是 Turing 架构(sm75),没有 FP8 硬件支持、没有 Transformer Engine、单卡 22GB 显存——按主流观点,这种卡"只能跑蒸馏版",是 2026 年本地部署 27B 级模型的"第三世界公民"。

但我们实测的结果是:27B 稠密模型(Qwen3.8)可以在 2080 Ti 双卡上以 ~74 tok/s 的 decode 速度运行,138K 上下文,带 MTP3 投机解码,接入 agent 长期稳定工作。

这不是靠魔法,而是靠:

  1. 一个专门适配 sm75 的 vLLM fork(vLLM-2080Ti-Definitive);
  2. 正确的量化格式选择(FP8 权重 + FP16 KV);
  3. 针对散热/显存/架构的调优参数组合。

本文把所有可复现的步骤、命令、参数和踩坑一次性写全。


二、环境速写(全部实测值,2026-08 验证)

GPU 2 × RTX 2080 Ti 22GB(GPU3 水冷 / GPU4 机箱底部风冷涡轮)
GPU 互联 NVLink ×2(nvidia-smi nvlink 核实存在)
CPU 24 核(具体型号见 lscpu
内存 125 GiB
系统 Ubuntu 24.04
驱动 580.178.04(CUDA 13.0 支持)
Python 3.11(fork 脚本自动建 venv,不需要 conda
fork vLLM-2080Ti-Definitive v0.1.15(sm75 编译,生产线主用)
模型 Qwen3.8-27B-FP8(官方仓库,目录 /home/m4k0/AI/2LLM/Qwen_Qwen3.8-27B-FP8
启动器 start-qwen2080ti.sh(8 个方案,默认方案 8)

NVLink 的存在是 TP=2 不被通信拖后腿的关键。nvidia-smi nvlink --getthroughput d 可看到 Link 0/1。

部署架构


三、从零部署:五步走

第 1 步:建环境与装 torch(10 分钟)

fork 提供了 setup_dependencies.sh 自动建 venv,一路 conda 的教程先放一边——我第一次就走了弯路(见踩坑 #1)。

# 装 torch(cu128 构建,2.11.0)
proxychains pip install torch torchvision torchaudio \
  --index-url https://download.pytorch.org/whl/cu128

# 验证
python -c "import torch; print(torch.cuda.is_available(), torch.__version__)"
# True 2.11.0+cu128

第 2 步:克隆并编译 fork sm75 内核(30–60 分钟)

cd ~/AI/2LLM
proxychains git clone https://github.com/weicj/vLLM-2080Ti-Definitive.git
cd vLLM-2080Ti-Definitive
bash setup_dependencies.sh   # 自动建 .venv、拉依赖、编译 sm75 CUDA kernels

构建输出:

Expected time: 30-60 minutes on a typical dual RTX 2080 Ti host
Build parallelism: CPU threads: 24 / System memory: 125GB / MAX_JOBS: 8 (auto-cap)

第 3 步:下载模型

Qwen3.8-27B-FP8 从 Hugging Face 官方拉取(~27GB)。本机路径:

/home/m4k0/AI/2LLM/Qwen_Qwen3.8-27B-FP8

第 4 步:启动(1 条命令)

写一个启动器(13.5KB,含 8 个方案),核心:

# 默认启动方案 8(FP8 + MTP3 + FP16-KV + 138K 上下文)
./start-qwen2080ti.sh

等价的完整 vllm serve 命令(方案 8,把启动器里的 --set 注入全部展开成 CLI 参数,原样可复现):

source .venv/bin/activate
cd ~/AI/2LLM/vLLM-2080Ti-Definitive
VLLM_USE_FLASHINFER_MOE_FP16=0 \
CUDA_VISIBLE_DEVICES=3,4 \
python -m vllm.entrypoints.openai.api_server \
  --model /home/m4k0/AI/2LLM/Qwen_Qwen3.8-27B-FP8 \
  --served-name qwen27b-fp8-fp16kv-138K-mtp3-text-diy-cu128 \
  --tensor-parallel-size 2 \
  --gpu-memory-utilization 0.93 \
  --max-model-len 138000 \
  --kv-cache-dtype auto \
  --enable-prefix-caching \
  --max-num-seqs 1 \
  --max-num-batched-tokens 2048 \
  --num-speculative-tokens 3 \
  --compilation-config '{"cudagraph_mode":"PIECEWISE","cudagraph_capture_sizes":[4],"max_cudagraph_capture_size":4}' \
  --enable-auto-tool-choice \
  --tool-call-parser qwen3_xml

三个关键点对照(和"为什么"见第 5 节):

  • --max-model-len 138000 是我实测出来的上限,不是官方 /profiles 里现成的值(官方最高 128K),见 §5.2;
  • --cudagraph_mode 必须是 PIECEWISE,不是 FULL_AND_PIECEWISE——后者在 MTP 多 shape 续跑时会读错 KV,这是第 4 篇专门修的坑;
  • --num-speculative-tokens 3 就是 MTP3,配合 PIECEWISE 才稳。

服务端就绪后 http://127.0.0.1:8000/v1/chat/completions 即为 OpenAI 兼容接口。

第 5 步:冒烟测试 + 测速

curl -s http://127.0.0.1:8000/v1/models | python3 -m json.tool | head

# 流式测速(decode + TTFT)
python3 test-speed.py   # 合成负载 + 真实短对话,输出 tok/s

四、8 个方案怎么选?先分清"官方 + 自己调的"

启动器内置 8 个方案。这里先说清楚一件事:这些方案不全是官方的。 官方 /profiles 里 27B 只有 5 个(见 profiles/README.md 的 Qwen3.x 27B 表),而 user/ 目录 + ...-diy 后缀的(方案 6/7/8)是我自己在官方基础上实测调整出来的。特别是 138K(方案 8)是我自己实测跑出的上限,官方根本没有这个档。

下表按启动器实际生效的 profile(start-qwen2080ti.shcase)列出来源与上下文:

方案 来源 profile(实际生效) 上下文 KV 说明
1 官方 normal/fp8/fp16kv-128K-mtp3-text-only.env(覆盖 MAX_MODEL_LEN=98304) 96K FP16 官方 normal 档,速度均衡
2 官方 fast/fp8/fp16kv-112K-mtp3-text-only.env(覆盖 98304) 96K FP16 官方 fast 档,短任务
3 官方 normal/fp8/int8kv-252K-mtp3-text-only.env(覆盖 131072) 128K INT8 官方 INT8,省显存
4 官方 fast/fp8/tqk8v4-256K-mtp3-text-only.env 256K TQK8V4 官方 TQK8V4,超长文档
5 官方 fast/fp8/tqk8v4-240K-mtp3-text-image.env(覆盖 196608) 192K TQK8V4 官方图文(text+image)
6 DIY user/fp8-fp16kv-96K-mtp3-text-diy.env(GPU_UTIL 0.93) 96K FP16 自己调,长速度兼顾
7 DIY user/fp8-fp16kv-96K-mtp3-text-image-diy.env(覆盖 81920) 80K FP16 自己调,图文
8 DIY user/fp8-fp16kv-138K-mtp3-text-diy.env(GPU_UTIL 0.93) 138K FP16 自己实测出的长上下文档(默认)
  • user/ 目录 + diy 后缀 = 我自己调的,不是官方;官方最高只有 FP16-KV 128K(normal)/112K(fast)。
  • 方案 8 的 MAX_MODEL_LEN=138000 是我实测出的 22GB×2 上限(不是官方表里的值),见 §5.2。user/fp16kv-138K 这个 env 文件里也写了:"MAX_MODEL_LEN=138000 是 22GB×2 实测上限,不是 1381024=141312(会 OOM)"*。
  • 实测速度(prefill/decode)完整数据见第 3、5 篇;本表重在"来源 + 能跑多大上下文",避免把官方和自调混为一谈。

附带一句:早期版本方案表把"官方/DIY"混在一起、上下文列也有出入(比如把方案 2 写成 96K 但没标它是 fast 档、方案 5 的 256K 实际是 192K),这次按启动器真实 profile 校正了。

注意:合成 decode 不等于真实对话速度,详见第 5 篇《长上下文性能》里的真实口径对照。


五、关键参数为什么这么定(调优详解)

这是本文最"干货"的部分。每个参数都有实测依据,不是抄来的默认值。

5.1 GPU_UTIL=0.93——0.92/0.93/0.94/0.95 实测边界

GPU_UTIL=0.92:MEMORY_PROFILING 报 "Free GPU memory (20.08/21.96 GiB) < model (13.62)
             + non_torch (1.28) + KV (5.94)"  → 差 0.1GiB,方案 8 启动不了
GPU_UTIL=0.93:✅ 最轻余量的 FP16 138K 可跑档(最终采用)
GPU_UTIL=0.94:✅ FP16 138K 可跑档,余量更大
GPU_UTIL=0.95:首次 smoke 跑完即
             "RuntimeError: CUDA error: an operation failed
              within mandatory range in FlashInfer workspace"  —— 显存耗尽

结论GPU Util 高 ≠ 更好。0.95 触发 FlashInfer workspace 报错,0.93 是本机 138K 档的临界可用值。

5.2 MAX_MODEL_LEN=138000——是我实测的,不是官方给的

  • 138K 这个值本身是我自己测出来的本机上限,官方 /profiles 里 27B 最高只有 FP16-KV 128K(normal)/ 112K(fast)。覆盖官方档上不去的区间,是我在 user/ 目录手写的 fp8-fp16kv-138K-mtp3-text-diy.env
  • 为什么是 138000 而不是 128×1024=131072、也不是 138×1024=141312:FP16-KV + 22GB×2 下,MAX_MODEL_LEN=138000 是刚好能跑起的墙(上游 PR #117 同结论)。141K 会 OOM:"KV cache (5.94 GiB) exceeds Free (5.92 GiB) by 0.02 GiB"——就差这 0.02GiB。
  • 比官方 96K(方案 1/2)多约 44% 上下文,真实 agent 长会话实测可容纳 100K+ tokens(见第 5 篇)。
  • 结论:如果你想抄,别以为把 --max-model-len 设成 138000 是"官方标配"——它是咱们这卡实测卡出来的边界值,换卡、换 KV 格式都要重新试。

5.3 KV dtype:FP16 ⇢ tqk8v4 ⇢ INT8

这是 2080 Ti 最关键的量化决策(详见第 3 篇),简版:

KV dtype 决策依据
fp16kv(FP16) 质量最高档,32K–138K 短会话首选
tqk8v4(tq4 量化) 超长档(256K+)速度最优,精度贴近 FP16
int8kv(INT8) 超长档显存最省,但短会话场景 decode 掉半(47 vs FP16 113),不推荐

5.4 MTP_K=3——MTP3 投机解码

  • decode 速度 +73% vs MTP_K=0
  • 需要配合 compilation=cudagraph_mode=PIECEWISE不是 FULL_AND_PIECEWISE,那是会读错 KV 的坑,见踩坑 #2 / 第 4 篇);
  • 注意:MTP_K=3 ≠ 3 个 MTP 层,是每步投机 3 个候选 token。

5.5 CLI_MODE=fast(safe sync)⚠️ 踩坑 #2

fast / aggressive / nosync 三种模式:

  • fast(safe sync):最稳定。aggressive 启动时连续 3 次 ptxas(Triton JIT 编译)被 SIGTERM 杀死,最后回退到 fast;
  • aggressive 在本机有 JIT 竞态问题(两个 worker 同时编译触发 ptxas crash),不推荐

5.6 MAX_BATCHED_TOKENS=2048

2048 是 138K 档的"唯一可行值":

  • 调大无提速(prefill 是算力瓶颈,不是 batch 瓶颈,见第 5 篇);
  • 8192 会挤爆 KV 破 138K 上限(启动失败)。

调优心得:2080 Ti 上"少即是多"。默认最大的 batch、最大的上下文、最高的 KV 利用率,踩坑成本非常高。先从小往大加。

5.7 enable_auto_tool_choice=1 + tool_call_parser=qwen3_xml

没有这两个参数,agent 模式下 tool_choice: "auto" 直接 HTTP 400

Invalid request: 'auto' tool choice requires --enable-auto-tool-choice
and --tool-call-parser to be set

这两个参数是"接入 agent"的硬性依赖——不带上,vLLM 只当 chat API 用;带上后才能被 PilotDeck/Claude Code 等 agent 客户端当工具调用引擎。

5.8 VIDEO_FLAGS——多模态默认关

--set "LANGUAGE_MODEL_ONLY=1" 默认关。开启图文视频要改 profile(详见第 7 篇)。


六、FAQ:你一定会问的 5 个问题

Q1:这真的是"部署"吗,还是魔改?
A:vLLM 源码零改动,改动只在两层:官方 profiles/(如 normal/fp8/fp16kv-128K)+ 我在 user/ 目录自己写的 ...-diy.env(138K/96K 那几个)+ 启动器 --set 注入(tool-choice / PIECEWISE / GPU_UTIL)。官方 profile 我没动,自己调的放在 user/ 且文件名带 diy 便于区分,删掉 --set 就能回到官方行为。fork 本体来自 github.com/weicj/vLLM-2080Ti-Definitive(v0.1.15,sm75 编译)。

Q2:为什么不用官方 vLLM?
A:sm75(Turing)在官方主线里是"最低支持",FP8 kernel、MTP、FlashInfer 这些关键路径需要 fork 的编译适配。官方主线直接跑 sm75 基本都是"能启动但性能拉胯"或"直接 OOM"。

Q3:没有 NVLink 可以吗?
A:理论可以,但 PCIe 3.0 双卡 TP=2 的 All-Reduce 延迟会拖慢 decode,实测 NVLink 上 74 tok/s,无 NVLink 预估掉到 50-60 tok/s(⬜ 未实测)。有 NVLink 强烈建议用。

Q4:27B 够不够用?
A:对我们的 agent 场景完全够。Qwen3.8-27B 在 MMLU、code、reasoning 上已接近 GPT-4o 的水平(社区评测口径),本地部署是性价比最高的选择。

Q5:怎么监控和回退?
A:

./start-qwen2080ti.sh status    # 看当前方案、显存、tok/s
./start-qwen2080ti.sh monitor   # 持续采样
./start-qwen2080ti.sh 96K       # 切回方案(96K)
./start-qwen2080ti.sh stop

切方案前会先停当前服务,启动新方案后自动写日志。


七、实测引用

以下数据来自 测试数据/int4-bench-results.txt(GPTQ-Int4 系列基准)、Q8优化方案与理由-2026-08-20.md(FP8 系列)和 start-qwen2080ti-复测优化总结-2026-08-20.md(方案 8 对比),全部为 2026-08-17 至 2026-08-31 本机测试。

关键锚点:

  • 冷启动 prefill(131K)≈ 45s(见第 5 篇);
  • 前缀缓存命中率 86% 时,TTFT 摊薄到 ~10s;
  • 真实短对话 decode 60–92 tok/s 区间(视方案),合成口径高于此(MTP 推测接受率高);
  • 138K 上下文满载:decode 无断崖(~80 tok/s 收敛至 ~65–74)。

八、本地路径与复现脚本清单(全部已验证存在)

路径 用途
~/AI/2LLM/vLLM-2080Ti-Definitive fork 本体(v0.1.15)
~/AI/2LLM/Qwen_Qwen3.8-27B-FP8 主模型权重
~/AI/2LLM/twolven_Qwen3.8-27B-abliterated-AWQ-MTP_q4 AWQ 版(多模态场景)
~/Desktop/2080Ti-vLLM项目/start-qwen2080ti.sh 8 方案启动器(主入口)
~/Desktop/2080Ti-vLLM项目/start-qwen2080ti-int4.sh INT4 35B 启动器
~/Desktop/2080Ti-vLLM项目/测试脚本/ 全套测试工具(测速/上下文扫描/散热监控)
~/Desktop/2080Ti-vLLM项目/文档总结/ 各次实验总结
~/.pilotdeck/pilotdeck.yaml agent 侧模型注册(热重载)

九、踩坑清单(部署阶段最痛的 3 个)

踩坑 1:conda 不是必需的(但很多人第一反应是建 conda env)
现象:source ~/.bashrc && conda activate 失败,torch 报错找不到。
根因:fork 的 setup_dependencies.sh 自己建了 .venv(项目内),与 conda 冲突。
解决:source .venv/bin/activate 而非 conda。
教训:✅ 先 clone,看脚本,再考虑 env——这是一条线。

踩坑 2:aggressive 模式启动必炸 ptxas(JIT 竞态)
现象:aggressive / nosync 模式启动,Triton JIT 编译(ptxas 子进程)被 SIGTERM 杀死,连续 3 次。
根因:两个 TP worker 同时做 Triton JIT 编译触发竞态;ptxas 被杀(上限 6 个 worker 被强制 restriction)。
解决:回退到 fast 模式(safe sync)。
教训:读日志要读对张量编译的 iterator,不是只看 TENSOR name——本次最初误判成 QUANT 张量,后纠正。

踩坑 3:27B FP8 模型 vLLM 默认 kv-cache-dtype 是 fp16,会 OOM
现象:VLLM Model Runner V2 下的 27B 模型首次启动报 "KVCacheMemoryFullError"。
根因:FP8 权重大小 ~13.6GiB(每卡),加上 FP16-KV + 138K 上下文,刚好顶爆 FlashInfer workspace。
解决:--gpu-memory-utilization 0.93 + MAX_MODEL_LEN 设 138K(不是更高)。
教训:GPU_UTIL 高 ≠ 好,先跑 0.92 看 OOM 边界,再逐步加到 0.93。


本文由基于实际部署记录编写。数据均来自 2026-08 本机实测(命令/输出均可回证)。遵循《部署总结写作方法论 v3》:事实分层(✅ 本地核实 / 🛠 本地改动 / ⬜ 未执行)、命令+结果佐证、关系级断言可回证。

posted @ 2026-09-04 21:35  M4K0  阅读(8)  评论(0)    收藏  举报