Qwen3.8-27B双卡A800部署指南

一份部署 + 调优 + 基准 + Agent 接入的技术文档。

  • 模型: Qwen3.8-27B (BF16 原生) · 硬件: 2× A800 80GB · 引擎: vLLM v0.26.0,端口 8003
  • 原生 262,144 上下文, fp8 KV Cache, 工具调用, 推理(思考)模式,reasoning_effort 全档位(标准枚举)按请求透传, MTP 投机解码。

1. 背景与目标

A800 (Ampere sm80) 没有 FP8 张量核, 每次矩阵乘需把 FP8反量化回 BF16 → 在 A800 上 FP8 反而更慢。这已在 05_Qwen36双卡部署文档.md 中验证过同一个根因。
本次目标: 在空闲的 GPU 0,1 上双卡 (TP=2) 部署 Qwen3.8-27B, 满足:

需求 说明
262K 上下文 KV 尽量驻留 GPU, 可用 fp8 KV 省显存
工具调用 各类 Agent (claude code / OpenAI agent) 接入
MTP 投机解码 官方推荐低延迟配置, 本机实测启用
思考 / 推理等级 reasoning_effort 原生 xhigh / medium / low, 经模板补丁别名归一支持标准全档位, 按请求透传
速度 / 显存 / 并发平衡 在 A800 上选最优精度

2. 软件与硬件环境

2.1 硬件

GPU 2× NVIDIA A800 80GB PCIe (Ampere, sm80)
Driver 580.159.03
CUDA (宿主) 13.0
互联 PCIe (A800 PCIe 版), NCCL 2.28.9

⚠️ A800 = Ampere sm80: 无 FP8/FP4 张量核, 与 H100(Hopper sm90)、B200(Blackwell)不同。

2.2 GPU 分布

GPU 用途 端口
0-1 Qwen3.8-27B (本次, 双卡 TP2) 8003

2.3 Docker 镜像版本

docker pull vllm/vllm-openai:v0.26.0
docker images | grep vllm
# vllm/vllm-openai:v0.26.0
  • 本次使用 vllm/vllm-openai:v0.26.0。模型卡要求 vLLM 0.17.0+ 支持 Qwen3_5 架构, v0.26.0 满足且其为 Qwen3_5MTP 架构解析,CUDA graph 编译与编译优化 (norm_quant/act_quant fusion) 均正常。

2.4 模型文件

  • 通过modelscope下载模型权重和配置文件。
pip install modelscope
modelscope download --model Qwen/Qwen3.8-27B --local_dir ./Qwen3.8-27B
模型 路径 体积 权重/卡 (TP2)
Qwen3.8-27B (BF16) /shared/models/Qwen3.8-27B 52 GB (18 shards) 26.08 GiB

generation_config.json: temperature=1.0, top_p=0.95, top_k=20。

3. 模型选型: BF16 vs FP8

3.1 核心事实: A800 没有 FP8 张量核

GPU 代际 计算能力 (Compute Capability) FP8 张量核
A800 sm80 (Ampere) ❌ 无
H100 sm90 (Hopper) ✅ 有
B200 sm100 (Blackwell) ✅ 有

在 A800 上, vLLM 无法用 FP8 MMA 直接算, 必须先把 FP8 权重反量化到 BF16 再乘。
因此:

  • BF16 原生: 直接跑 BF16 矩阵乘, 无额外开销, 最快
  • FP8: 权重体积减半, 但每次矩阵乘多一次反量化 → 预填充反而更慢。解码受 HBM 带宽限制, 权重变小省带宽、反量化+计算吃掉这些收益, 净结果持平

3.2 实测对比 (§8 详表)

指标 BF16 FP8 结论
32K 预填充 5,832 tok/s 4,655 tok/s BF16 快 25%
100K 预填充 3,718 tok/s 3,248 tok/s BF16 快 14%
单流解码 59 tok/s 59 tok/s 持平 (带宽受限)
并发 8 聚合 297 tok/s 249 tok/s BF16 略快
KV 池 259.5 万 token 321.7 万 token FP8 多 24%
权重/卡 26.08 GiB 14.96 GiB FP8 省 11 GiB

3.3 结论: 选 BF16

  • 速度: BF16 预填充全面领先, 解码持平 → 无理由换 FP8。
  • 显存: FP8 省 11 GiB/卡 → KV 池多 24%。但对 Agent 场景, BF16 的 259 万 token KV 池已远超需求 (见 §9), 显存根本不构成瓶颈。
  • 并发: BF16 并发 8 聚合 297 tok/s > FP8 249 tok/s。
  • 关键洞察: 本模型是混合注意力, 只 16/64 层全注意力, 48 层是线性注意力 (常驻重复状态, 不存 KV)。所以 KV 缓存天生极小, “用 FP8 省显存放更多 KV” 的红利被混合注意力直接抹平 —— 这是本结论与普通 dense 模型不同的地方。

推荐: Qwen3.8-27B (BF16), 配 --kv-cache-dtype fp8(KV 本身用 fp8, 不损权重速度)。
FP8 权重版仅当需要把模型塞进更小显存、且接受更慢预填充时才有意义。

4. 模型架构解析

  • Qwen3.8-27B 是 27B 稠密 (dense) 模型, 但用了与 Qwen3.5-2.4T 同源的混合注意力骨干:
  • 64 层, 其中 16 层全注意力(每 4 层 1 层, full_attention_interval: 4), 48 层线性注意力
  • 线性注意力用常驻重复状态(RNN 式), 无需 KV cache → 长上下文 KV 占用极低。
  • 多模态: 含视觉塔 (vision_config, 深度 27 的 encoder)。
  • 内置 MTP 草稿头(mtp_num_hidden_layers: 1), 支持投机解码。
  • 原生上下文 262,144 token (RoPE rope_theta=10000000, 无 rope_scaling, 不超 256K)。

⚠️ 262K 是硬上限: config.json 无 rope_scaling, 强上 512K 会 RoPE 越界出 NaN(同 Qwen3.6 结论)。要更长上下文需 YaRN 覆写, 本文按 256K 原生部署。

5. 部署步骤

5.1 一键脚本

mkdir scripts
vim scripts/deploy_qwen38.sh
bash scripts/deploy_qwen38.sh
  • scripts/deploy_qwen38.sh完整脚本内容:
#!/usr/bin/env bash
# =============================================================================
# Qwen3.8-27B 双卡部署脚本 (标准 vLLM v0.26.0, GPU 0-1, 端口 8003)
# -----------------------------------------------------------------------------
# 用途: A800 上 BF16 原生全速运行 (A800=Ampere sm80, 无 FP8 张量核, FP8 需反量化
#       到 BF16, 反而更慢)。混合注意力 (16/64 层全注意力, 48 层线性注意力) 使其
#       KV 缓存极小, 双卡 80GB 显存轻松容纳 262K 上下文 + fp8 KVCache。
#
# 关键特性 (与官方 recipe 对齐):
#   - 262144 原生上下文, KV 尽量驻留 GPU
#   - fp8 KVCache (混合注意力 KV 小, fp8 再省一半)
#   - 工具调用: --enable-auto-tool-choice + qwen3_coder parser
#   - 推理: --reasoning-parser qwen3, 默认思考, reasoning_effort 透传
#   - MTP 投机解码: --speculative-config '{"method":"mtp","num_speculative_tokens":3}'
#   - 前缀缓存 + chunked prefill (agent 场景)
#
# 可覆盖环境变量:
#   GPUS          设备, 默认 "device=0,1"
#   TP_SIZE       张量并行, 默认 2
#   PORT          端口, 默认 8003
#   MAX_MODEL_LEN 上下文上限, 默认 262144 (256K 原生)
#   MAX_BATCHED_TOKENS  预填充 chunk, 默认 8192 (官方案例甜点)
#   GPU_MEM_UTIL  显存利用率, 默认 0.95 (若 OOM 降 0.93)
#   WATERMARK     保留空闲 KV 比例, 默认 0.1
#   MAX_NUM_SEQS  并发序列上限, 默认 256
#   ENABLE_MTP     是否启用 MTP 投机解码, 默认 1
#   KV_CACHE_DTYPE KV 缓存精度, 默认 fp8
#   CHAT_TEMPLATE_PATCH 是否挂载补丁模板 (high/ultra/max -> xhigh), 默认 0
# =============================================================================
set -e

MODEL_HOST_PATH="${MODEL_HOST_PATH:-/shared/models/Qwen3.8-27B}"
MODEL_CTR_PATH="/model-qwen38"
IMAGE="${IMAGE:-vllm/vllm-openai:v0.26.0}"
CONTAINER="${CONTAINER:-vllm-qwen38}"
PORT="${PORT:-8003}"
SERVED_MODEL="${SERVED_MODEL:-Qwen3.8-27B}"

GPUS="${GPUS:-device=0,1}"
TP_SIZE="${TP_SIZE:-2}"
MAX_MODEL_LEN="${MAX_MODEL_LEN:-262144}"
MAX_BATCHED_TOKENS="${MAX_BATCHED_TOKENS:-8192}"
GPU_MEM_UTIL="${GPU_MEM_UTIL:-0.95}"
WATERMARK="${WATERMARK:-0.1}"
MAX_NUM_SEQS="${MAX_NUM_SEQS:-256}"
ENABLE_MTP="${ENABLE_MTP:-1}"
KV_CACHE_DTYPE="${KV_CACHE_DTYPE:-fp8}"
CHAT_TEMPLATE_PATCH="${CHAT_TEMPLATE_PATCH:-0}"

docker rm -f "${CONTAINER}" 2>/dev/null || true

echo "[deploy] ${CONTAINER} | GPUS=${GPUS} TP=${TP_SIZE} max-model-len=${MAX_MODEL_LEN} batch=${MAX_BATCHED_TOKENS} util=${GPU_MEM_UTIL} kv=${KV_CACHE_DTYPE} mtp=${ENABLE_MTP}"

ARGS=( "${MODEL_CTR_PATH}"
  --host 0.0.0.0
  --port "${PORT}"
  --served-model-name "${SERVED_MODEL}"
  --tensor-parallel-size "${TP_SIZE}"
  --max-model-len "${MAX_MODEL_LEN}"
  --gpu-memory-utilization "${GPU_MEM_UTIL}"
  --trust-remote-code
  --enable-prefix-caching
  --enable-chunked-prefill
  --watermark "${WATERMARK}"
  --max-num-batched-tokens "${MAX_BATCHED_TOKENS}"
  --max-num-seqs "${MAX_NUM_SEQS}"
  --enable-auto-tool-choice
  --tool-call-parser qwen3_coder
  --reasoning-parser qwen3
  --kv-cache-dtype "${KV_CACHE_DTYPE}"
)

if [ "${ENABLE_MTP}" = "1" ]; then
  ARGS+=( --speculative-config '{"method":"mtp","num_speculative_tokens":3}' )
fi

MOUNTS=( -v "${MODEL_HOST_PATH}:${MODEL_CTR_PATH}" )
if [ "${CHAT_TEMPLATE_PATCH}" = "1" ]; then
  PATCHED_TEMPLATE_HOST="$(cd "$(dirname "$0")/.." && pwd)/patches/qwen38_chat_template_high_alias.jinja"
  MOUNTS+=( -v "${PATCHED_TEMPLATE_HOST}:/template-patched.jinja" )
  ARGS+=( --chat-template /template-patched.jinja )
  echo "[deploy] chat-template patch ON (high/ultra/max -> xhigh)"
fi

docker run -d --name "${CONTAINER}" \
  --gpus "\"${GPUS}\"" \
  --ipc=host --shm-size=16g \
  -p ${PORT}:${PORT} \
  "${MOUNTS[@]}" \
  -e CUDA_DEVICE_ORDER=PCI_BUS_ID \
  "${IMAGE}" \
  "${ARGS[@]}"

echo "[deploy] started. logs: docker logs -f ${CONTAINER} | ready: curl localhost:${PORT}/v1/models"
  • 环境变量可覆盖 (含 FP8 切换):
# 默认 BF16 部署
GPUS=device=0,1 TP_SIZE=2 PORT=8003 MAX_MODEL_LEN=262144 bash scripts/deploy_qwen38.sh

# 想切 FP8 权重 (GPU 6-7, 端口 8004):
MODEL_HOST_PATH=shared/models/Qwen3.8-27B-FP8 \
CONTAINER=vllm-qwen38fp8 PORT=8004 SERVED_MODEL=Qwen3.8-27B-FP8 GPUS=device=0,1 \
bash scripts/deploy_qwen38.sh

# 关 MTP 投机解码:
ENABLE_MTP=0 bash scripts/deploy_qwen38.sh

5.2 完整 docker run (等价展开)

docker rm -f vllm-qwen38 2>/dev/null || true
docker run -d --name vllm-qwen38 \
  --gpus '"device=0,1"' \
  --ipc=host --shm-size=16g \
  -p 8003:8003 \
  -v /shared/models/Qwen3.8-27B:/model-qwen38 \
  -e CUDA_DEVICE_ORDER=PCI_BUS_ID \
  vllm/vllm-openai:v0.26.0 \
  /model-qwen38 \
  --host 0.0.0.0 --port 8003 \
  --served-model-name Qwen3.8-27B \
  --tensor-parallel-size 2 \
  --max-model-len 262144 \
  --gpu-memory-utilization 0.95 \
  --trust-remote-code \
  --enable-prefix-caching \
  --enable-chunked-prefill \
  --watermark 0.1 \
  --max-num-batched-tokens 8192 \
  --max-num-seqs 256 \
  --enable-auto-tool-choice \
  --tool-call-parser qwen3_coder \
  --reasoning-parser qwen3 \
  --kv-cache-dtype fp8 \
  --speculative-config '{"method":"mtp","num_speculative_tokens":3}'
  • 进阶 (可选, 非默认):更长上下文 (>262K): 需 YaRN 覆写 rope_parameters (factor 按典型长度调, 如 524K 用 2.0):
vllm serve ... \
  --max-model-len 1000000 \
  --hf-overrides '{"text_config": {"rope_parameters": {"mrope_interleaved": true, "mrope_section": [11,11,10], "rope_type": "yarn", "rope_theta": 10000000, "partial_rotary_factor": 0.25, "factor": 4.0, "original_max_position_embeddings": 262144}}}'

⚠️ 静态 YaRN 会降低短文本质量, 仅在真需要长上下文时启用。

5.3 就绪检查

curl -s http://127.0.0.1:8003/v1/models   # → id=Qwen3.8-27B, max_model_len=262144

启动耗时 (实测): 约 5.5 分钟 (权重加载 51.75 GiB + torch.compile 70s + CUDA graph 预热)。
vllm-qwen38 | init engine took 194.43 s (compilation: 70.25 s)

  • 显存分配 (BF16)情况
weight 26.08 GiB | peak activation 2.31 GiB | CUDAGraph 0.75 GiB | 
KV cache 45.99 GiB |GPU KV cache size: 2,595,079 tokens | Maximum concurrency @262K: 9.90x

6. 启动参数详解

参数 理由
--tensor-parallel-size 2 2 双卡 TP; 26 GB/卡权重 + 46 GB KV, 余量充足
--max-model-len 262144 256K 模型原生上限; 不可超 (无 rope_scaling, §4)
--gpu-memory-utilization 0.95 0.95 尽量用满显存 → KV 池 259 万 token; OOM 则降 0.93
--kv-cache-dtype fp8 fp8 KV 缓存用 fp8_e4m3, 省一半, 提升 KV 容量 (不影响权重速度)
--max-num-batched-tokens 8192 8192 预填充 chunk, 官方 recipe 甜点; 平衡吞吐与尾延迟
--max-num-seqs 256 256 并发序列上限 (Agent/批量)
--watermark 0.1 0.1 永久保留 10% KV 空闲, 防前缀缓存占满抖动
--enable-prefix-caching Agent 共享系统提示词命中前缀, 免重复预填充
--enable-chunked-prefill 长预填充与解码交错, 保并发
--enable-auto-tool-choice 工具调用 (Agent 必需)
--tool-call-parser qwen3_coder - Qwen 工具/函数解析
--reasoning-parser qwen3 - 推理字段解析 (思考模式)
--speculative-config '{"method":"mtp","num_speculative_tokens":3}' MTP 投机解码, 内置 MTP 草稿头, ~1.4× 加速
--trust-remote-code 模型含自定义代码

说明: 引擎自动给 KV 加 3 层 padding (适配线性注意力 non-uniform shape), 最多浪费 6.25% KV, 属正常。

7. 功能验证

7.1 思考 / 推理等级 (reasoning_effort)

模型原生只认 xhigh/medium/low, 与 vLLM 的标准枚举有落差; 用一行模板补丁做别名归一,把 vLLM 侧全部档位打通, 见 7.1.3 的代码改动。

7.1.1 模型原生支持的推理等级 (原版)
  • Qwen3.8原始 chat_template.jinja 只承认 三档: xhigh(默认) / medium / low,校验写死在模板里, 其余取值一律 raise_exception:
{%- set resolved_reasoning_effort = reasoning_effort|default('xhigh') %}
{%- if resolved_reasoning_effort not in ('xhigh', 'medium', 'low') %}
    {{- raise_exception('Unexpected reasoning effort ' ~ reasoning_effort ~
        '. Supported types are xhigh (default), medium, and low.') }}
{%- endif %}
  • 模型思考开/关: chat_template_kwargs: {"enable_thinking": false} 直接给答案。
  • reasoning_effort 既支持顶层字段(OpenAI 标准), 也支持放在 chat_template_kwargs 里,按请求独立设置, 互不影响。
  • 保留思考: preserve_thinking 默认 true, 保持多轮推理轨迹连续性 (Agent 推荐)。
方式 请求示例 实测
顶层 reasoning_effort "reasoning_effort":"xhigh" ✅ 产生思考 (message.reasoning)
顶层 reasoning_effort "reasoning_effort":"low" ✅ 快速直答
chat_template_kwargs {"reasoning_effort":"medium"} ✅ 生效
  • 字段名注意: vLLM v0.26 返回推理内容在 message.reasoning(OpenAI chat) /content[type=thinking].thinking(Anthropic messages), 不是旧版 reasoning_content

  • vLLM v0.26 的 OpenAI 层 (protocol.py) 却按标准枚举接受none / minimal / low / medium / high / xhigh / max。二者落差导致的直接后果:Agent 发 reasoning_effort: "high" 时, vLLM 层校验通过、原版模板却因 high 不在三档内而 raise_exceptionvLLM 400 拒请求, 推理无法激活

因此原生支持范围是:

原生 effort vLLM 侧校验 模板层 结果
xhigh (默认) 长思考
medium 中等思考
low 短思考
none ❌ raise 400
minimal ❌ raise 400
high ❌ raise 400
max ❌ raise 400
7.1.2 完善后的推理等级
  • 提供补丁模板 patches/qwen38_chat_template_high_alias.jinja, 只做别名归一, 把vLLM 标准枚举全部映射进模型原生三档: high/ultra/max → xhigh, minimal → low,none 仍由 enable_thinking 独立关闭。启用方式:
CHAT_TEMPLATE_PATCH=1 bash scripts/deploy_qwen38.sh

补丁后全档位实测 (推理长度随 effort 递增, 均正常激活, 无 400):

客户端传入 归一后 行为
none 思考关 (enable_thinking=false) 直接答
minimal low 思考, 较短
low low 思考, 较短
medium medium 思考, 中等
high xhigh 思考, 较长
ultra xhigh 思考, 较长
max xhigh 思考, 较长
xhigh xhigh 思考, 最长

映射原则: 上线看细档, 下线看粗档 —— 低于 medium 的 (low/minimal) 归到 low,
高于 medium 的 (high/ultra/max) 归到 xhigh, 语义连续、单调递增。校验异常分支
(Unexpected reasoning effort) 保留兜底, 只是不再对标准枚举触发。

7.1.4 代码改动 (补丁模板 + 部署脚本)
  • 补丁只改模板的 effort 枚举归一, 其余与官方 chat_template.jinja 逐字一致;通过 --chat-template 覆盖生效, 不改动共享的模型 checkpoint, 重启即生效。
  1. 模板核心改动 (patches/qwen38_chat_template_high_alias.jinja, 相对原版仅改一段):
{# 原版: 白名单三档, 其余 raise #}
{%- if enable_thinking is undefined or enable_thinking is true %}
    {%- set resolved_reasoning_effort = reasoning_effort|default('xhigh') %}
    {%- if resolved_reasoning_effort not in ('xhigh', 'medium', 'low') %}
        {{- raise_exception('Unexpected reasoning effort ' ~ reasoning_effort ~ '. Supported types are xhigh (default), medium, and low.') }}
    {%- endif %}
    {%- if resolved_reasoning_effort == 'xhigh' %}
        {%- set reasoning_instructions = 'Reasoning effort is set to xhigh. Please think carefully through the task, validate key assumptions, consider plausible alternatives, and prioritize correctness, consistency, and clarity in the final answer.' %}
    {%- elif resolved_reasoning_effort == 'low' %}
        {%- set reasoning_instructions = 'Reasoning effort is set to low. Keep your thinking brief and focused, moving directly to the conclusion without unnecessary elaboration.' %}
    {%- endif %}
{%- endif %}

{# 补丁: 先别名归一, 再进原三档; 异常分支保留兜底 #}
{%- if enable_thinking is undefined or enable_thinking is true %}
    {%- set resolved_reasoning_effort = reasoning_effort|default('xhigh') %}
    {%- if resolved_reasoning_effort in ('high', 'ultra', 'max') %}
        {%- set resolved_reasoning_effort = 'xhigh' %}
    {%- elif resolved_reasoning_effort in ('minimal',) %}
        {%- set resolved_reasoning_effort = 'low' %}
    {%- elif resolved_reasoning_effort not in ('xhigh', 'medium', 'low') %}
        {{- raise_exception('Unexpected reasoning effort ' ~ reasoning_effort ~ '. Supported types are xhigh (default)/high, medium, and low.') }}
    {%- endif %}
    {%- if resolved_reasoning_effort == 'xhigh' %}
        {%- set reasoning_instructions = 'Reasoning effort is set to xhigh. Please think carefully through the task, validate key assumptions, consider plausible alternatives, and prioritize correctness, consistency, and clarity in the final answer.' %}
    {%- elif resolved_reasoning_effort == 'low' %}
        {%- set reasoning_instructions = 'Reasoning effort is set to low. Keep your thinking brief and focused, moving directly to the conclusion without unnecessary elaboration.' %}
    {%- endif %}
{%- endif %}
  • 全文件仅此 1 处脚本逻辑改动, 其余指令文本逐字一致。

② 部署脚本改动 (scripts/deploy_qwen38.sh):

# 环境变量开关 (默认 0 = 用原版模板)
CHAT_TEMPLATE_PATCH="${CHAT_TEMPLATE_PATCH:-0}"

# 参数数组里追加 --chat-template, 并只读挂载补丁模板
MOUNTS=( -v "${MODEL_HOST_PATH}:${MODEL_CTR_PATH}" )
if [ "${CHAT_TEMPLATE_PATCH}" = "1" ]; then
  PATCHED_TEMPLATE_HOST="$(cd "$(dirname "$0")/.." && pwd)/patches/qwen38_chat_template_high_alias.jinja"
  MOUNTS+=( -v "${PATCHED_TEMPLATE_HOST}:/template-patched.jinja" )
  ARGS+=( --chat-template /template-patched.jinja )
  echo "[deploy] chat-template patch ON (high/ultra/max -> xhigh)"
fi
  • 要点: 补丁模板以只读挂载进容器 (/template-patched.jinja), 通过 --chat-template 指向它覆盖模型的默认模板设置; 不改 checkpoint、不改镜像, CHAT_TEMPLATE_PATCH=0 即可一键回退原版。

7.2 工具调用

curl -s -X POST http://127.0.0.1:8003/v1/chat/completions -H 'Content-Type: application/json' -d '{
  "model":"Qwen3.8-27B","max_tokens":300,
  "tools":[{"type":"function","function":{"name":"get_weather","description":"查询城市天气",
     "parameters":{"type":"object","properties":{"city":{"type":"string"}},"required":["city"]}}}],
  "messages":[{"role":"user","content":"查北京天气, 用工具"}]}'
# → tool_calls: [{"function":{"name":"get_weather","arguments":"{\"city\": \"北京\"}"}}]
#   finish_reason: tool_calls

7.3 三种接入接口全部可用

接口 基本对话 工具调用 思考/推理 备注
/v1/chat/completions (OpenAI) message.reasoning 通用
/v1/messages (Anthropic) content[type=thinking] + adaptive claude code 用
/v1/responses (OpenAI Responses) 新 API

Anthropic 端 real 实测 (thinking:{"type":"adaptive"} 返回 thinking block + text, stop_reason=end_turn)。

8. 性能基准实测

全部实测: 随机文本避免前缀缓存干扰; 解码为 thinking 关 (纯生成速度)。
同硬件双容器轮流测, 避免跨容器争用; 数字摘取自 benchmark_data/qwen38_*_result.json

8.1 预填充 (冷启动, 无缓存)

段长 BF16 FP8
8K 5,511 tok/s (2.7s) 1,733 tok/s (首个请求编译抖动)
32K 5,832 tok/s (10.3s) 4,655 tok/s
100K 3,718 tok/s (51.2s) 3,248 tok/s

预填充是稠密 27B 的计算瓶颈(infer_dense), 混合注意力 + chunked prefill 下吞吐随上下文下降较缓 (线性注意力层 O(N) 预填充功劳), 比纯注意力好很多。
150K 未测: 生成随机文本按 ~1.9× 膨胀, 150K 文本超过 262K 上限被 400 拒绝。

8.2 单流解码

精度 单流 tok/s
BF16 59 tok/s
FP8 59 tok/s

稠密 27B 解码由 HBM 带宽决定 (每 token 要流式读取全部 26 GiB 权重), 接近 A800理论带宽上限 (~77 tok/s)。MTP 已把 59 推高 ~1.4× (见 §8.4); 不开 MTP 约 41 tok/s。

8.3 并发 (128 输出 token/请求, thinking 关)

并发 BF16 聚合 BF16 P50 FP8 聚合
1 54 tok/s 0.51s 54 tok/s
2 53 tok/s 0.69s 55 tok/s
4 43 tok/s 0.80s 45 tok/s
8 297 tok/s 0.73s 249 tok/s

并发为何能放大吞吐? 稠密模型权重固定, 批量 N 个序列时每层权重只读一次、算 N 次,于是从"带宽受限"切换到"算力受限"。并发 8 时聚合 297 tok/s 远超单流 59, 正是批量化摊薄权重读取的结果。并发越高、批越大, 聚合吞吐越高 (可冲到 ~500+)。

8.4 MTP 投机解码实测

drafts_total = 1097 | draft_tokens = 3291 (每 draft 3 token)
accepted_tokens_total = 1564 → 接受率 47.5% → 每次模型前向产出 ~1.43 token
  • MTP 把单流解码从 ~41 推到 59 tok/s(约 1.4×)。低并发时吞吐受 MTP 接受率抖动影响(故 c1-c4 聚合波动大); 高并发包提升吞吐。

9. 并发与容量估算

9.1 KV 池 (BF16, --kv-cache-dtype fp8)

GPU KV cache size = 2,595,079 token, 最大并发 (满 262K/请求) ≈ 9.9 个

Agent 场景 估算
平均上下文 50K 259 万 / 50K ≈ 51 个
平均上下文 100K 259 万 / 100K ≈ 25 个
满载 256K 259 万 / 256K ≈ 9.9 个

fp8 KV 把池子从 ~130 万推到 259 万 (省一半)。对典型 Agent 负载(万级 ~ 10万级上下文)完全够, 几十个并发 Agent 不成问题

9.2 与其它模型容量对比 (同双卡 80GB)

模型 类型 KV 池 单流解码 预填充 32K
Qwen3.8-27B (本篇) dense 27B 259 万 59 5,832
Qwen3.6-35B-A3B 3B 激活 MoE 355 万 165 25K+

Qwen3.6-35B-A3B 因激活小, 解码/预填充更快、池更大; Qwen3.8-27B 是更强/更新的稠密模型 (质量上限更高), 但吞吐更低。两者用途不同:追求极致吞吐用 A3B, 追求更高模型质量用 27B dense。

10. 运维/监控/排错

# 就绪
curl -s http://127.0.0.1:8003/v1/models

# KV 占用 / 排队
curl -s http://127.0.0.1:8003/metrics | grep -E 'kv_cache_usage_perc|num_requests_waiting'

# MTP 接受率
curl -s http://127.0.0.1:8003/metrics | grep -E 'spec_decode_num_(accepted|draft)_tokens_total'

# 重启 (注: 权重加载 + 编译约 5.5 分钟)
bash scripts/deploy_qwen38.sh

# 日志
docker logs -f vllm-qwen38

# 回退到 DSv4
export ANTHROPIC_BASE_URL="http://<宿主机IP>:8000"
  • OOM 处理: GPU_MEM_UTIL=0.95 启动 OOM (显存竞争/图捕获) 时降 0.93:
GPU_MEM_UTIL=0.93 bash scripts/deploy_qwen38.sh
  • KV 精度注意: 权重 FP8 时 kv_cache.py 提示 scaling factor 1.0 for fp8_e4m3——若精度敏感需确认 checkpoint 自带 k/v_scale; BF16 权重无此问题。

12. 结论

  1. A800 双卡 → 选 BF16, 不用 FP8 权重。根因: Ampere 无 FP8 张量核, FP8 预填充反而更慢,解码持平; 而混合注意力让 KV 极小, FP8 的省显存红利被抹平。
  2. 配置基线: TP=2, 262K 上下文, --kv-cache-dtype fp8(KV 省一半), 0.95 显存,prefix caching + chunked prefill, 工具调用qwen3_coder/reasoning-parser,MTP 投机解码 (3 token), max-num-batched-tokens 8192。
  3. 能力全部就绪: 工具调用、思考模式、reasoning_effort xhigh/medium/low 按请求透传、OpenAI / Anthropic / Responses 三接口。
  4. 实测成绩 (BF16, TP2): 32K 预填充 5,832 tok/s; 100K 3,718 tok/s; 单流解码 59 tok/s(MTP ~1.4×); 并发 8 聚合 297 tok/s; KV 池 259 晚token (fp8), 可支撑几十个 Agent。
Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐