基于 Qwen3.5-35B-A3B MoE 在 RTX 5090 (32GB) 上的实战经验。 从 vLLM 5 tok/s 折腾到 llama.cpp 190 tok/s 的全过程。
| 指标 | 最终配置 | 原始配置 (vLLM) |
|---|---|---|
| 引擎 | llama.cpp | vLLM 0.17.1 Docker |
| 模型 | Qwen3.5-35B-A3B (MoE, 3B active) | Qwen3.5-27B-AWQ (dense) |
| Decode 速度 | 190 tok/s | 5 tok/s |
| 翻译延迟 | 0.2s (thinking off) | 60s |
| 最大上下文 | 178K tokens (实测 needle 通过) | ~20K |
| 并发 | 4 路 | 1 路 |
| 日产量 (4路满载) | 27M tokens/天 | ~2M |
| 启动时间 | 5s (mlock) | 2+ min |
RTX 5090 = Blackwell = sm_120。必须 CUDA 12.8+。
- 系统默认 PyTorch 2.6 + cu124 →
no kernel image is available直接报错 - 解决:用
~/llm/vllm_venv(PyTorch 2.10 + cu128) 或 llama.cpp 自编译 (cu130)
确认编译时包含 sm_120:
system_info: CUDA : ARCHS = 1200 | USE_GRAPHS = 1 | FA_ALL_QUANTS = 1 | BLACKWELL_NATIVE_FP4 = 1
在 RTX 5090 上测了 vLLM 0.17.1 和 0.18.1,都有严重问题:
| 问题 | 原因 |
|---|---|
--enforce-eager 必须开 |
不开 CUDA graphs 会在首次请求时 OOM crash |
| 开了 enforce-eager 后 ~5 tok/s | 27B dense 模型在 eager mode 下极慢 |
--enable-prefix-caching crash |
Qwen3.5 hybrid attention + Mamba cache "experimental",直接崩 |
| cu130-nightly (0.18.1) 视觉 encoder 吃光显存 | KV cache 从 36K 缩到 6K tokens |
--gpu-memory-utilization > 0.82 crash |
高 util + enforce-eager + 首次请求 = OOM |
结论:vLLM 在单卡 5090 + Qwen3.5 hybrid attention 上不好使。
| 模型 | 格式 | 大小 | tok/s | 推荐 |
|---|---|---|---|---|
| Qwen3.5-35B-A3B UD-Q4_K_XL | GGUF | 19 GB | 190 | ✅ 最佳 |
| Qwen3.5-35B-A3B MXFP4_MOE | GGUF | 21 GB | 167 | ❌ 更慢更大 |
| Qwen3.5-27B Q4_K_M (dense) | GGUF | 16 GB | 66 | ❌ 慢 3x |
UD-Q4_K_XL = Unsloth Dynamic 量化,重要层用更高精度。
| KV 格式 | tok/s | 质量 | 推荐 |
|---|---|---|---|
| F16 / F16 | 195 | baseline | |
| Q4_0 K + Q8_0 V | 188 | 无损 | ✅ |
| Q4_0 / Q4_0 | 186 | 无损 | OK |
| Q5_0 / Q5_0 | 153 | 无损 | ❌ 反而慢 |
| IQ4_NL / IQ4_NL | 38 | 无损 | ❌ 5x 慢,别用 |
IQ4_NL 是坑:理论上信息最优,实际计算开销巨大。
Qwen3.5 默认开 thinking (<think>...</think>),翻译一句话花 400 tokens 在推理上。
| 控制方式 | 效果 | 适用 |
|---|---|---|
--reasoning off (全局) |
所有请求关闭 thinking | 纯翻译服务 |
--reasoning auto (默认) + 客户端控制 |
按请求开关 | ✅ 混合场景 |
--reasoning-budget 100 |
限制 thinking 最多 100 tokens |
客户端关闭 thinking(推荐):
{
"chat_template_kwargs": {"enable_thinking": false}
}Python SDK:
resp = client.chat.completions.create(
model="gpt-4o",
messages=[...],
extra_body={"chat_template_kwargs": {"enable_thinking": False}},
)A3B MoE 只有 10 个 full-attention 层(64 层中),KV cache 极其省:
| Context | Prefill 时间 | VRAM 增长 | Needle 命中 |
|---|---|---|---|
| 9K | 1.2s | +0 MB | ✅ |
| 53K | 6.6s | +88 MB | ✅ |
| 107K | 16.3s | +192 MB | ✅ |
| 178K | 34.9s | +330 MB | ✅ |
模型训练长度 262K。32GB 卡实测可到 178K+。
| 并发 | 聚合吞吐 | 单请求 tok/s |
|---|---|---|
| 1 | 190 | 190 |
| 2 | 290 | 145 |
| 4 | 356 | 96 |
混合负载 30 秒压测(2 agent + 2 翻译):313 tok/s → 日产 27M tokens。
# /etc/systemd/system/sglang.service
[Unit]
Description=Qwen3.5-35B-A3B via llama.cpp (Text + Vision)
After=network.target
[Service]
Type=simple
Restart=always
RestartSec=5
TimeoutStartSec=300
ExecStart=/16tb/llm/llama.cpp/build/bin/llama-server \
-m /16tb/llm/Qwen3.5-35B-A3B-GGUF/Qwen3.5-35B-A3B-UD-Q4_K_XL.gguf \
--mmproj /16tb/llm/Qwen3.5-35B-A3B-GGUF/mmproj-F16.gguf \
-ngl 99 \
-c 131072 \
-np 4 \
--host 0.0.0.0 \
--port 18082 \
--flash-attn on \
-ctk q4_0 \
-ctv q8_0 \
--mlock \
--cont-batching \
--cache-prompt \
--reasoning-format deepseek
[Install]
WantedBy=multi-user.target# /etc/systemd/system/llm-translate.service
[Unit]
Description=Qwen2.5-1.5B Translation (CPU only)
After=network.target
[Service]
Type=simple
Restart=always
RestartSec=5
ExecStart=/16tb/llm/llama.cpp/build/bin/llama-server \
-m /16tb/llm/Qwen2.5-1.5B-Instruct-Q8.gguf \
-ngl 0 \
-c 4096 \
-np 4 \
--host 0.0.0.0 \
--port 18083
[Install]
WantedBy=multi-user.target如果有 GTX 1060 6GB 等闲置小卡,改成 -ngl 99 offload 到 GPU,翻译速度从 14 tok/s → ~50 tok/s。
| 方案 | 压缩 | 在 llama.cpp 上 |
|---|---|---|
llama.cpp -ctk q4_0 |
4x | ✅ 一个参数,原生内核 |
| TurboQuant (0xSero) | ~5x | ❌ 需要 Python + vLLM |
| 我们的 Tiered (Hot/Warm/Cold) | 理论 7.5x | ❌ 研究阶段 |
| vLLM FP8 KV | 2x | ❌ 只在 vLLM |
llama.cpp 原生的 Q4 KV cache 已经 4x 压缩、质量无损、速度几乎不掉。 TurboQuant 的理论更优(无偏估计),但工程上 llama.cpp 的简单 per-group 量化更实用。
- Layer 0 必须跳过:Qwen2.5 第一层 K 的 norm 是其它层的 40x,TurboQuant 旋转假设失效
- 2-bit V 不可用:39% 相对误差,生成崩溃
- cos sim 骗人:attention output cos=0.88 看起来 OK,但实际生成是乱码
- 真正有效:2-tier (hot 5% FP16 + warm 95% 4b+4b) 在 1.5B 上 100% greedy match
| 坑 | 现象 | 解决 |
|---|---|---|
| 默认 PyTorch 不支持 sm_120 | CUDA kernel error | 用 vllm_venv 或 llama.cpp |
| vLLM enforce-eager | 5 tok/s | 换 llama.cpp |
| vLLM CUDA graphs | 首次请求 OOM crash | 加 --enforce-eager(治标)或换 llama.cpp |
| vLLM prefix-caching + Qwen3.5 | 启动后 crash | 别用(experimental) |
| cu130-nightly 视觉 encoder | KV cache 缩到 6K | 用 0.17.1 latest 或 llama.cpp |
| IQ4_NL KV cache | 5x 慢 | 用 Q4_0 |
| MXFP4 模型权重 | 比 UD-Q4_K_XL 慢 10% | 用 UD-Q4_K_XL |
| Thinking 模式吃 token | 翻译一句话 400 tokens | enable_thinking: false 或 --reasoning off |
| Q5 KV cache | 比 Q4 还慢 | 用 Q4 K + Q8 V |