单张 A100 32GB SM80 显卡,跑 27B 混合注意力模型,256K 上下文 + MTP 投机解码 + 工具调用全开。本文记录完整的部署参数、踩坑过程和实测速度表。
1. 硬件环境
| 部件 | 规格 |
|---|---|
| GPU | NVIDIA DRIVE-PG199-PROD,32 GiB 显存(车载版本A100),SM80(Ampere,compute capability 8.0) |
| CPU | Intel Xeon W-2125 @ 4.00GHz(6 核) |
| 内存 | 40 GB |
| 驱动 / CUDA | NVIDIA 595.84;CUDA 13.3 工具链(随 Python 环境安装) |
| 存储 | 1 TB(模型 + 环境占用约 60GB) |
几个平台特性,后面所有决策都和它们有关:
- SM80 没有原生 FP8 张量核(fp8e4nv 是 SM89+/SM90 才有的)——这是 KV cache 压缩方案选择的分水岭;
- 显卡为车规功耗设计,长时间满载后温度 44→74°C,SM 时钟稳定 1260MHz,无降频。
2. 模型:Qwen3.8-27B-AWQ-MTP
社区量化版 shawnw3i/Qwen3.8-27B-AWQ-MTP(基于 Qwen/Qwen3.8-27B):
- 27.3B dense,混合注意力:64 层 = 48 层 linear_attention(GDN)+ 16 层 full_attention,每 4 层一次全注意力;
- 多模态(27 层视觉塔),训练上下文 262,144 tokens;
- AWQ W4A16(group_size 128,Marlin kernel),磁盘 19GB:11.4GB int4 打包权重 + 6GB 未量化的 BF16 部分(embedding、lm_head、linear_attn 的 in_proj、视觉塔、MTP 模块)——"18GB 模型"里只有 65% 是 Q4;
- 内置 MTP(Multi-Token Prediction)投机解码模块(1 层 transformer,权重在
model_extra_tensors.safetensors),与主模型共享 embedding/lm_head,显存只多 ~3GB; - 下载工具:
ftllm download <repo> --tool aria2c -x 8 -j 4(多线程秒级拉满带宽)。
3. 部署:vLLM 0.27.1 + pm2
软件栈
- Python 3.13.13 + uv 虚拟环境(
/root/llm/.venv),vLLM 0.27.1(PyPI 最新),torch 2.13.0+cu130,flashinfer 0.6.16.post3; - pm2 托管(
vllm-qwen38-27b-awq,端口 5000,OpenAI 兼容 API)。
最终启动命令(run-qwen38-27b-awq.sh)
export VLLM_USE_FLASHINFER_SAMPLER=0 # 绕开 flashinfer 采样 kernel 的 CUB 编译崩溃(见 §5)
vllm serve /root/llm/models/Qwen3.8-27B-AWQ-MTP \
--host 0.0.0.0 --port 5000 \
--attention-backend FLASHINFER \
--kv-cache-dtype fp8 \
--max-model-len 262144 \
--reasoning-parser qwen3 \
--enable-auto-tool-choice \
--tool-call-parser qwen3_coder \
--speculative-config '{"method":"mtp","num_speculative_tokens":3}' \
--max-num-seqs 1 \
--gpu-memory-utilization 0.98 \
--served-model-name qwen38-27b-awq-mtp \
--enable-log-requests \
--enable-prompt-tokens-details \
--enable-prefix-caching
关键参数为什么这么定(全部经过实测对比):
| 参数 | 选择 | 理由 |
|---|---|---|
--attention-backend FLASHINFER |
FlashInfer | SM80 上唯一能 fp8 KV 的后端;长上下文 decode 稳定(见 §4.2) |
--kv-cache-dtype fp8 |
fp8 | KV 池 285,575 tokens,是 fp16 的 2 倍,撑起 256K 上下文 |
--max-model-len 262144 |
训练上限 | KV 池恰好覆盖完整 256K |
--speculative-config MTP × 3 |
3 drafts | 2/3/4 实测,3 是全场景最优点(§4.3) |
--max-num-seqs 1 |
单并发 | MTP 能装进 32GB 的关键(Mamba cache / graph 池 / 工作区全压到单槽) |
--gpu-memory-utilization 0.98 |
压满 | 32GB 卡跑 27B 的余量策略 |
--tool-call-parser qwen3_coder |
XML 模板 | 模型 chat template 是 <tool_call> XML 格式 |
运行态:显存 30.4/32 GiB,启动 ~90 秒(kernel 编译缓存命中),pm2 save 持久化。
4. 实测速度
测试方法:每个上下文长度用独立随机文本(开头互不相同,避免 prefix cache 跨长度污染);cold=首次请求,warm=同 prompt 二次请求(命中前缀缓存);生成任务为写 ~1500 字说明文(约 800~1000 tokens,保证 decode tps 准确);MTP 接受率取自
/metrics计数器差值。
4.1 最终配置总览(FLASHINFER + fp8 + 256K + MTP3)
| 上下文 | 模式 | TTFT | prefill t/s | decode t/s | MTP 接受率 | 缓存命中 |
|---|---|---|---|---|---|---|
| 0 | cold | 0.15s | 231* | 68.8 | 46.7% | 0 |
| 1K | cold | 0.34s | 2,391 | 69.6 | 46.4% | 0 |
| 32K | cold | 10.36s | 2,372 | 70.1 | 47.4% | 0 |
| 32K | warm | 1.17s | 21,034 | 70.1 | 47.4% | 22,400 |
| 128K | cold | 60.38s | 1,625 | 69.8 | 49.1% | 0 |
| 128K | warm | 2.05s | 48,086 | 69.8 | 49.1% | 96,000 |
| 240K | cold | 155.35s | 1,184 | 62.0 | 48.5% | 0 |
| 240K | warm | 4.33s | 42,522 | 62.0 | 48.5% | 180,800 |
* 0 上下文仅 33 tokens,prefill 被固定开销摊薄,无参考意义。
要点:
- decode 跨上下文几乎恒定(62~70 t/s)——这是 FlashInfer split-KV 内核的功劳(对比见下);
- prefix cache 命中时 240K 的 TTFT 只要 4.3 秒(冷启动 155s),固定前缀场景体验接近短上下文;
- 长上下文 cold prefill 变慢是物理规律(attention 计算量随长度增长)。
4.2 注意力后端 × KV 精度对比(同一写作任务、MTP3)
| 上下文 | FLASH_ATTN + fp16 | FLASHINFER + fp16 | FLASHINFER + fp8 | TURBOQUANT 4-bit |
|---|---|---|---|---|
| 0 | 89.1 t/s | 69.0 | 68.8 | 143.4* |
| 1K | 91.3 | 71.2 | 69.6 | 141.1* |
| 32K | 54.2 | 70.8 | 70.1 | 63.0* |
| 128K | 22.1 | 61.1 | 69.8 | 86.7* |
| 240K | — | — | 62.0 | — |
| 最大上下文 | 128K | 128K | 256K | 256K(但输出崩坏) |
* TurboQuant 数字不可信:无校准 4-bit KV 导致长生成退化为重复循环(** ** ** / !!!!),速度测的是"循环复读机"。
结论:FA2 的 decode 成本随上下文线性增长(128K 掉到 22 t/s);FlashInfer 靠 split-KV(KV 切块多 SM 并行 + 在线 softmax 合并)保持恒定;fp8 KV 因为 KV 读取带宽减半,在 128K 还额外 +14%。
4.3 MTP draft 数量调优(2 / 3 / 4)
| 上下文 | spec=2 decode | spec=3 decode | spec=4 decode | 接受率 2/3/4 |
|---|---|---|---|---|
| 0 | 65.4 | 68.8 | 68.7 | 59% / 47% / 39% |
| 1K | 62.9 | 69.6 | 69.1 | 55% / 46% / 40% |
| 32K | 66.7 | 70.1 | 66.3 | 63% / 47% / 38% |
| 128K | 66.0 | 69.8 | 65.7 | 63% / 49% / 40% |
接受率随 draft 数单调下降(猜得越多越难命中),每步产出在 3 时最高(~1.9 token/步)。
4.4 任务类型对 decode 的影响(MTP 是内容相关的)
| 任务 | 单步 | decode | 说明 |
|---|---|---|---|
| 摘要 / 短问答(greedy) | 12.3ms | ~240 t/s | 输出模板化,接受率高 |
| 复述上文(100% 接受率) | 27.8ms | 115~144 t/s | 接受率拉满但 MTP 前向开销大 |
| 写 1500 字说明文 | ~28ms | 62~75 t/s | 自由长文,接受率 ~47% |
| 代码 / 工具调用 | — | 正常 | qwen3_coder parser 全通(含并行工具调用闭环) |
⚠️ 教训:"MTP 接受率高 = 快"不成立。复述任务接受率 100%,但每步要跑 3 次 MTP 前向(~28ms/步),比接受率 67% 的摘要(12.3ms/步)还慢。速度 = 接受率 × 单步吞吐,两者都要看。
4.5 与 llama.cpp 的对比(同一模型家族、同卡)
| 指标 | llama.cpp(Q4_K_M GGUF) | vLLM + FLASHINFER + MTP(AWQ) |
|---|---|---|
| decode(写作) | ~38 t/s | 62~70 t/s |
| prefill | ~950-1,000 t/s | 1,200~2,400 t/s |
| 最大上下文 | 203K(无 MTP) | 256K(带 MTP) |
| 长上下文 decode 稳定性 | 随长度下降 | 恒定 |
5. 踩坑记录(每个都花了真金白银的时间)
- flashinfer 0.6.16 + CUDA 13.3 的 CUB 头文件不兼容:JIT 编译
sampling.cuh报BlockAdjacentDifference has no member FlagHeads→ EngineCore 崩溃循环。解法:VLLM_USE_FLASHINFER_SAMPLER=0(采样走 vLLM 原生路径;attention kernel 用另一套头文件,不受影响)。 - SM80 开不了 fp8 KV? 默认后端确实不行:FLASH_ATTN 报"requires FA3 on SM90 or FA4 on SM100",TRITON 报"fp8e4nv requires SM89+"。但 FLASHINFER 后端支持 SM80 + fp8 KV(存储压缩 + 软件反量化),代码层面
supports_compute_capability: 8.0~12.1明确放行。别被第一个报错劝退。 - FA2 长上下文 decode 崩塌:batch=1 时 FA2 无并行度可挖,128K 上下文单步 90ms;FlashInfer 的 split-KV 把长 KV 切块并行,单步恒定 ~28ms。长上下文服务请直接选 FlashInfer。
- MTP 装不进 32GB:主模型 18.6GB + MTP 模块 ~3GB + KV + graph 池超出预算,报错还误导("MTP 需要再加载一份权重"是错的——日志确认
Sharing target model embedding weights,drafter 只多 ~1GB)。真正的解法是--max-num-seqs 1(Mamba cache / graph 池 / 工作区全部单槽化)。 - TurboQuant 无校准 4-bit KV 输出崩坏:短问答正常,长生成退化为
** ** **/!!!!循环。这类激进量化必须配 TurboQuant 校准过的 checkpoint,无校准变体(_nc)别碰。 - "隐形思考 token":vLLM 0.27 的 qwen3 reasoning parser 对 Qwen3.8 的思考 token 处理不完整——模型思考时 token 不计入 content 也不计入 reasoning_content(但计入 usage),表现为"生成 1600 tokens 正文 0 字"的假数据。测速时用
chat_template_kwargs: {"enable_thinking": false}规避;真实服务里则要留意长思考请求的"空回答"(社区建议服务端固定reasoning_effort: medium)。 - FlashInfer + 投机解码的 cudagraph 降级(vLLM #49547):自动降级 PIECEWISE,约 -16%,SM80 无 workaround(trtllm-gen 路径要 SM100+),等上游 RFC #49488。已知税,接受。
- prefix caching 别关:实测关闭后摘要 decode 从 240 掉到 127 t/s(Mamba cache 模式切换的副作用),还损失 warm 秒级 TTFT。
6. 结论
在 32GB SM80 平台上,Qwen3.8-27B-AWQ-MTP 的最优部署形态是:
FlashInfer 后端 + fp8 KV cache + 256K 上下文 + MTP×3 + 单并发 + prefix caching
- 写作类任务稳定 62~70 t/s(跨 0~240K 上下文持平),摘要/问答类 ~240 t/s;
- 256K 全量上下文 + 工具调用 + 推理模式全开;
- 相比 llama.cpp:decode 快 1.6~1.8 倍,prefill 快 2 倍以上,上下文从 203K 提到 256K;
- 硬件的物理边界(SM80 无 FP8 核、车规功耗)决定了 fp8 只能是"存储压缩",但这已经够用。
测试脚本:benchmarks/bench_ctx_cold_warm.py(冷/热 + MTP 接受率)、bench_ctx_cold_warm_128k.py(128K 变体)、bench_ctx_prefill_decode.py(prefill 专项)。
阅读:10 评论: 0 💬