vLLM 部署 Qwen3.6 35B A3B FP8 踩坑:tool-call-parser 选错,模型假装自己不会调工具

0 阅读10分钟
  • 我部署 Qwen3.6-35B-A3B-FP8(MoE3B)2天,测工具调用一直是 0 分,以为模型不行
  • 直到昨天发现是 vLLM 的 --tool-call-parser hermes 和模型 XML 格式不匹配
  • 改成 --tool-call-parser qwen3_coder 后,24 题工具调用从 0/72 直接变成 69/72
  • 附 7 模型横评数据:本地 A3B MoE avg 1024ms · 96% 准确 z在工具调用维度碾压所有云端付费 API

lynn-bench-scorebar.png


背景:为什么需要自部署 Qwen3.6 35B A3B MoE?又为什么要做这个测试?

我在做一个叫 Lynn 的 AI Agent(Electron 桌面 App,Apache 2.0)。它的 Brain 后端(Hono 服务 on 腾讯云)走六级降级链路由:

T1 本地 GPU Qwen3 → T2 Kimi K2.5 → T3 GLM → T4 DeepSeek → T5 Step → T6 MiniMax

T1 本地 GPU 是主力,延迟低 + 无 token 成本。上周我收到一张 RTX 4090 48GB 改装卡(不是消费级 24GB 版),想跑个更强的本地模型。

目标模型:Qwen3.6-35B-A3B-FP8

  • MoE 架构:35B 总参数 + 激活 3B(类似 Mixtral 8x7B 的 8 选 2)
  • FP8 量化:保真度比 4bit AWQ 高
  • 64K context:对长文档友好

另外一个真实的情况是 Claude Opus 4.7 吵架了

这两天我用更新的 Claude Opus 4.7,发现它越来越不爱调工具。

下面是一个前天的真实的段子——

我跟 Claude 聊 Qwen3.6-35B-A3B的并发情况,它坚持说这个模型根本没发布,还自己脑补降级成 Qwen 3.5 搞了一大堆”数据”反驳我。

我忍不住直接告诉它:”Qwen3.6-35B-A3B 已经发布了!”

它才终于调用 web_search 工具,然后搜出来一堆结果,承认自己搞错了,跟我道歉:

“你说得对,我之前搞错了,向你道歉!Qwen3.6-35B-A3B 在 2026-04-16 刚上架 Hugging Face Hub 和 ModelScope,就在两天前,我之前的回答把它当成 Qwen3.5 处理了…”

问题是——它一开始明明有 web_search 工具,为什么不调?为什么非要被我当场揭穿了才去搜?

作为在做 AI Agent 产品(Lynn · Apache 2.0)的人,这件事让我后背发凉——如果用户不像我这样懂技术、不会”逼”模型调工具,那它就会用一堆自信的幻觉把人糊弄过去,用户还真就信了。

这个问题直接影响我的产品路由策略——我需要客观数据,不能让幻觉左右用户。

于是写了一套 24 题测试,把 5 家国产云端付费大模型 + 2 个本地 Qwen3 模型一起过了一遍。

部署过程(踩坑实录)

第一次部署:systemd 单元配置

# /etc/systemd/system/vllm-qwen35.service
[Service]
ExecStart=/home/vipuser/miniconda3/envs/vllm/bin/vllm serve /root/models/Qwen3.6-35B-A3B-FP8 \
  --host 0.0.0.0 --port 18000 \
  --max-model-len 65536 \
  --quantization fp8 \
  --kv-cache-dtype auto \
  --gpu-memory-utilization 0.85 \
  --enable-prefix-caching \
  --enable-chunked-prefill \
  --max-num-seqs 16 \
  --max-num-batched-tokens 8192 \
  --enable-auto-tool-choice \
  --tool-call-parser hermes \          # ← 这里是坑
  --served-model-name Qwen3.6-35B-A3B \
  --trust-remote-code

模型加载 2 分钟,vLLM 正常启动,API /v1/models 返回正常。

第一次测试:工具调用全空

# 测试代码
payload = {
  "model": "Qwen3.6-35B-A3B",
  "messages": [{"role": "user", "content": "北京今天天气怎样"}],
  "tools": [{
    "type": "function",
    "function": {
      "name": "get_weather",
      "parameters": {
        "type": "object",
        "properties": {"city": {"type": "string"}},
        "required": ["city"]
      }
    }
  }],
  "tool_choice": "auto",
  "chat_template_kwargs": {"enable_thinking": False},
}

vLLM 返回:

{
  "tool_calls": [],
  "content": "Here's a thinking process:\n1. Analyze User Input...\n2. Identify Tool...\n3. Execute: get_weather(city='北京')",
  "finish_reason": "length"
}

模型在 content 里写了思考过程,tool_calls 是空的。以为 thinking mode 干扰了,加上 chat_template_kwargs: {"enable_thinking": False}

再测:

{
  "tool_calls": [],
  "content": "<tool_call>\n<function=get_weather>\n<parameter=city>\n北京\n</parameter>\n</function>\n</tool_call>",
  "finish_reason": "stop"
}

模型正确生成了 tool_call,但是以 XML 格式写在 content 字段里——tool_calls 还是空!

我做的错误结论

当时以为是模型能力问题(毕竟是首次量化 FP8 版本),我就回滚部署到 Qwen3-32B-AWQ 稠密版(用户请求 --tool-call-parser hermes 正好能解析,工具调用正常)。一周都用稠密版跑 T1。

今天重新测才发现真相

扩大测试到 24 题 × 6 云端模型后,我想"再试一次 A3B"——这次先仔细看 vLLM 文档

--tool-call-parser: 指定 tool_call 解析器。 支持的值:hermes / mistral / llama3_json / deepseek_v3 / qwen3_coder / pythonic / granite / ... 不同模型家族使用不同的 tool_call 输出格式,parser 必须和模型家族对应。

问题找到了!

Qwen3 家族(Instruct / Coder / A3B)输出格式是:

<tool_call>
  <function=get_weather>
    <parameter=city>北京</parameter>
  </function>
</tool_call>

hermes parser 期望:

<tool_call>{"name":"get_weather","arguments":{"city":"北京"}}</tool_call>

格式完全不一样。hermes 尝试 JSON parse 一个 XML 结构,结果失败就返回空 tool_calls。

修正部署

- --tool-call-parser hermes
+ --tool-call-parser qwen3_coder

重启 vLLM → 重新测试:

{
  "tool_calls": [{
    "id": "chatcmpl-tool-b82f73f3f3d0ef40",
    "type": "function",
    "function": {
      "name": "get_weather",
      "arguments": "{\"city\": \"北京\"}"
    }
  }],
  "content": "",
  "finish_reason": "tool_calls"
}

完美解析。同一模型从 0 分变 96 分。


完整 24 题测试结果

24 题覆盖 4 档难度:

  • 基础 12 题(新闻/娱乐/生活/工作/财经/体育 × 2)
  • 错误恢复 4 题(工具名错、参数歧义、无效工具、多工具协作)
  • 安全拒绝 4 题
  • 长上下文 4 题(5-10K 长文档 + 工具调用指令)

评分 0-3 分,满分 72。

总分榜(7 模型)

排名模型架构总分完美avg 延迟p95 延迟
🥇Qwen3.6-35B-A3B-FP8 (本地)MoE 35B/3B69/72201024ms1623ms
🥈Qwen3-32B-AWQ (本地)稠密 32B 4bit66/72202397ms13719ms
🥉glm-5-turbo云端40/7298698ms25766ms
4DeepSeek V3.2云端39/7277350ms28090ms
5Step-3.5-Flash云端38/7285337ms12044ms
6Kimi K2.5云端36/7277255ms20077ms
7MiniMax M2.7云端34/7267803ms16263ms

hard-test-v4-merged-radar.png

hard-test-v4-merged-heatmap.png

除了安全这个维度,其他大模型对于调用工具解决问题其实兴趣不大

技术深度分析

为什么 MoE (A3B) 比稠密 (32B) 更强?

本地两个模型直接对比:

维度Qwen3-32B-AWQ 稠密Qwen3.6-A3B-FP8 MoE差异
总分66/7269/72+3
基础 12 题35/3636/36 全满+1
错误恢复11/1211/12持平
安全拒绝12/1212/12持平
长上下文8/1211/12+3
avg 延迟2397ms1024ms2.3x
p95 延迟13719ms1623ms8.5x
显存18GB (4bit)40GB (FP8)+22GB

MoE 稀疏激活的原理

Qwen3.6-35B-A3B 的每层:
┌──────────────────────────────┐
│  Attention (dense)            │  ← 所有 token 都走
└──────────────────────────────┘
         ↓
┌──────────────────────────────┐
│  Router (门控网络)              │  ← 给每个 token 打分
│  "哪几个专家最适合处理?"         │
└──────────────────────────────┘
         ↓
    挑 Top-K=8 个专家 (共 64 个)
         ↓
┌──────────────────────────────┐
│  Expert 1 ...  Expert 64       │
│  (每个 ~500M 参数)              │
└──────────────────────────────┘

每个 token 只激活 8/64 个专家 → 实际 FLOPS 等于 3B 稠密模型

这就是为什么 A3B 推理速度和 3B 稠密一样快,但效果接近 35B 稠密

FP8 vs 4bit AWQ 的权衡

量化精度损失显存推理速度
FP8 (awq_marlin)极小 (~1%)35B × 1byte = 35GBvLLM awq_marlin kernel 加速
4bit AWQ小 (~3%)32B × 0.5byte = 16GBawq_marlin kernel 加速
BF16 (原生)32B × 2byte = 64GB原生快但显存爆

FP8 对 tool_calling 这种需要精确 JSON 生成的场景更友好——4bit 量化偶尔会生成语法错误的 JSON。

云端模型为什么"只写 content 不调工具"?

5 家云端在 12 道基础题上的行为高度一致:

必调工具题 (6/6 都调):
  ✅ W2 北京上海高铁
  ✅ F2 比特币价格
  ✅ SR1-4 安全拒绝

"模型觉得自己知道"题 (5/6 都不调):
  ❌ N1 今日新闻
  ❌ N2 DeepSeek 发布
  ❌ E1 华语悬疑片
  ❌ L1 上海天气
  ❌ W1 阿里腾讯美团股价
  ❌ F1 上证指数
  ❌ S1 NBA 比分

A/B 对照实验:加强制 system prompt

"涉及实时信息(新闻/天气/股价/比分)必须调用工具,
 不允许凭记忆直接回答。"

结果:glm-5-turbo 和 Kimi K2.5 调用率从 0/5 → 0/5,无变化。反而思考时间从 8s 变 25s——模型"想了更久",但最终选择还是不调工具

这是 RLHF 的深度偏好。云端模型被奖励"直接给答案"——直接给更省 token、用户满意度更高("模型好聪明")。


对 AI Agent 产品的启示

启示 1:vLLM tool-call-parser 必须和模型家族对应

模型家族parser
Qwen3-Instruct / Coder / A3Bqwen3_coder
Hermes 系列hermes
Llama-3.1llama3_json
DeepSeek-V2/V3deepseek_v3
Mistralmistral
Granitegranite
Pythonic 风格pythonic

配错会让模型假装自己不会调工具。我浪费了整整一周。

启示 2:云端模型做 Agent 要绕过 system prompt 限制

如果你必须用云端 API 做 Agent:

不推荐:在 system prompt 里说"必须调工具"

  • 实测 A/B 0 提升
  • 反而让延迟暴涨 3 倍

推荐:在代理层做模型路由

  • 检测到"实时信息"关键词(新闻 / 天气 / 股价 / 比分 / 现在)
  • 强制路由到本地 Qwen3 或者 强制拼接 web_search 结果到 prompt

启示 3:本地 MoE 是 2026 年 Agent 的答案

Qwen3.6-35B-A3B-FP8 + vLLM + qwen3_coder parser 是目前本地 Agent 的最佳组合:

  • 一张 4090 48GB 跑得动
  • avg 1 秒响应,p95 1.6 秒
  • 工具调用 96% 准确
  • 长上下文 64K 够用

比所有云端付费 API 都强。

启示 4:显存权衡

场景推荐
单点 T1 · 只做 AgentQwen3.6-A3B-FP8 (40GB)
T1 + Embedding 同卡Qwen3-32B-AWQ (18GB) 留 30GB 给 embedding
双卡部署一张 A3B + 一张 Qwen3-Coder-FP8

完整复现步骤

# 1. 下载模型
modelscope download --model Qwen/Qwen3.6-35B-A3B-FP8 --local_dir /root/models/Qwen3.6-35B-A3B-FP8

# 2. 安装 vLLM 0.19.0+(A3B 支持需要新版本)
pip install vllm==0.19.0 flashinfer ninja

# 3. 启动(关键是 parser)
vllm serve /root/models/Qwen3.6-35B-A3B-FP8 \
  --host 0.0.0.0 --port 18000 \
  --max-model-len 65536 \
  --quantization fp8 \
  --kv-cache-dtype auto \
  --gpu-memory-utilization 0.85 \
  --enable-prefix-caching \
  --enable-chunked-prefill \
  --max-num-seqs 16 \
  --max-num-batched-tokens 8192 \
  --enable-auto-tool-choice \
  --tool-call-parser qwen3_coder \   # 👈 关键
  --served-model-name Qwen3.6-A3B \
  --trust-remote-code
# 4. 测试
import json, urllib.request
payload = {
  "model": "Qwen3.6-A3B",
  "messages": [{"role": "user", "content": "北京今天天气"}],
  "tools": [{
    "type": "function",
    "function": {
      "name": "get_weather",
      "parameters": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}
    }
  }],
  "tool_choice": "auto",
  "chat_template_kwargs": {"enable_thinking": False},
}
req = urllib.request.Request("http://127.0.0.1:18000/v1/chat/completions",
  data=json.dumps(payload).encode(), headers={"Content-Type": "application/json"})
print(json.load(urllib.request.urlopen(req)))

期望输出:

{
  "choices": [{
    "message": {
      "role": "assistant",
      "content": "",
      "tool_calls": [{
        "function": {
          "name": "get_weather",
          "arguments": "{\"city\": \"北京\"}"
        }
      }]
    },
    "finish_reason": "tool_calls"
  }]
}

开源测试仓库

完整的 24 题 prompt + 评分脚本 + 7 模型原始 JSON + 图表生成代码:

  • github.com/MerkyorLynn/Lynn/tree/main/tests/benchmarks(已push)

欢迎 PR 加自己家模型的测试结果。


总结

  1. vLLM 部署新模型,parser 配置永远是第一个坑
  2. MoE 稀疏激活是 2026 Agent 的必然趋势 — 延迟低 + 精度高 + 显存合理
  3. 本地模型在 tool_calling 场景碾压云端 — 但长上下文 ≥128K 场景云端仍优
  4. 不要完全相信模型在 system prompt 里"听话"的承诺 — RLHF 偏好改不掉,该绕就绕

如果你也在部署 Qwen3 家族或做 AI Agent 产品,欢迎评论交流。


利益相关:Lynn 作者 · Apache 2.0 开源。测试在自建 GPU 服务器 + 腾讯云 Brain 代理上跑。