一张游戏卡跑出60t/s:我用Qwen3.6-27B搭了个Coding Agent
你手头只有一张RTX 3090(24GB),目标是在本地跑一个能写代码、能看图、能调工具的27B模型。官网说支持128K上下文,但你一开128K就崩,多轮对话从60t/s断崖式掉到10t/s,连打字都卡。这不是模型的问题,是部署调优的路径没走对。这篇文章记录了我在单张3090上驯服Qwen3.6-27B的完整过程——从踩坑到甜点配置,数据说话,代码可复制。
场景原型:我需要一个本地Coding Agent
某个产品经理丢来一个需求:“能不能让AI在本地帮我们写Python脚本、改代码,还能截图给我看?” 核心要求:
– 模型参数量27B(够用且能塞进24GB显存)
– 支持function calling(调工具、搜文档)
– 支持视觉多模态(截图识别 + 代码理解)
– 多轮对话不能掉速(一个任务经常要改20个来回)
当时摆在面前有两个推理引擎:SGLang 和 llama.cpp。SGLang对MoE模型优化极好(快5-7×),但冷启动要200秒,单卡跑27B的Dense模型性能反而不如llama.cpp。而llama.cpp冷启动只要4.5秒,显存控制更精细。最终选了 llama.cpp server-cuda。
模型选Qwen3.6-27B,原因是它采用 Gated DeltaNet + Attention混合架构:65层中只有16层是传统Attention(带KV cache),剩下49层是无需KV cache的Gated DeltaNet。这意味着:
– KV cache只有传统模型的 1/4
– 同样显存能塞更长上下文
– 原生支持MTP(Multi-Token Prediction)投机解码
第一轮试水:开箱即崩
我直接按官方示例,配了128K上下文,加上 GGML_CUDA_GRAPH_OPT=1 想加速,结果:
– 首次请求确实快,接近 59 t/s
– 多轮对话到第3轮,速度暴跌到 10-13 t/s
– 再往后,每轮都有2-3秒延迟,明显卡顿
查日志发现:每次KV cache长度变化,都会触发CUDA Graph重录,而那49层Gated DeltaNet的图编译本身就慢。这是最大的坑——关掉 GGML_CUDA_GRAPH_OPT 后,多轮对话速度立刻稳定在 59-65 t/s。
关键调优的三大发现
1. 对称量化KV cache比不对称快17%
对比测试:
| KV Cache 配置 | 速度 t/s | 质量 |
|---|---|---|
| f16 / f16 | 64.2 | 最佳 |
| q8_0 / q8_0(对称) | 64.5 | 接近f16 |
| q8_0 / q4_0(不对称) | 55.1 | 略差 |
对称量化可以让GPU一次性加载K和V的8-bit块,省去类型转换。所以最终选择 --cache-type-k q8_0 --cache-type-v q8_0。
2. MTP投机解码的正确甜点
MTP (Multi-Token Prediction) 让模型一次预测多个token,然后由draft模型验证。测试结果:
| MTP n-max | 速度 t/s | 相比基线提升 |
|---|---|---|
| 0(关) | 40.7 | — |
| 1 | 55.2 | +36% |
| **2** | **63.2** | **+55%** |
| 3 | 60.1 | +48%(收益递减) |
选 --spec-draft-n-max 2,draft模型的KV cache用q4_0量化(省显存)。
3. 为什么放弃128K,选64K?
原始目标128K,但实测问题:
– 显存不够:24GB显存 + 系统swap,128K下生成时速度降为 6.5 t/s(32K时还有17 t/s)
– 虚拟内存瓶颈:超过32K后,每token处理时间从14ms涨到154ms
– 实际coding agent场景:30轮对话才到50K左右,64K完全够用
换成64K后,显存剩余约4GB,多轮对话全程稳定59-65 t/s。
完整配置:一张3090上的甜点
以下Docker Compose配置已稳定运行数周,用于搭建Coding Agent后端:
services:
lla-qwen27b:
image: ghcr.io/ggml-org/llama.cpp:server-cuda
container_name: lla-qwen27B-gpu0
restart: unless-stopped
runtime: nvidia
ports:
- "11434:8080"
volumes:
- ./models:/models:ro
environment:
- CUDA_VISIBLE_DEVICES=0
command: >
-m /models/Qwen3.6-27B-UD-Q4_K_XL.gguf
--mmproj /models/mmproj-Qwen3.6-27B-f16.gguf
--host 0.0.0.0 --port 8080
--ctx-size 65536
--cache-type-k q8_0 --cache-type-v q8_0
--flash-attn on
--image-min-tokens 1024
--parallel 2
--n-predict 16384
-ngl 99 -t 8 -tb 4 -b 2048 -ub 1024
--no-mmap --no-mmproj-offload
--spec-type draft-mtp --spec-draft-n-max 2
--spec-draft-type-k q4_0 --spec-draft-type-v q4_0
显存占用稳定在 20.5 GB(83%),剩余约4GB做buffer。
实际效果:长上下文下的速度衰减
虽然64K比128K好很多,但Attention的O(n)本质无法改变。速度随上下文增长而下降:
| 上下文长度 | 纯Decode速度 | 每 token 处理时间 |
|---|---|---|
| 100 | ~60 t/s | ~14ms |
| 1K | ~45 t/s | ~22ms |
| 8K | ~17 t/s | ~59ms |
| 32K | ~6.5 t/s | ~154ms |
对Coding Agent的影响:新任务(2-3K)秒回;修改几轮后(15-20K)预填几秒,生成仍快;深入20+轮(35-50K)预填需要20-30秒,可以接受;如果到64K满负荷,建议开新task清理历史。
关于工具调用和多模态
llama.cpp的OpenAI兼容API原生支持function calling,无需额外参数。一个典型请求:
curl http://localhost:11434/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "Qwen3.6-27B",
"messages": [{"role":"user","content":"搜索人工智能最新进展"}],
"tools": [{
"type": "function",
"function": {
"name": "search_web",
"description": "搜索互联网",
"parameters": {
"type": "object",
"properties": {"query": {"type": "string"}},
"required": ["query"]
}
}
}],
"tool_choice": "auto",
"max_tokens": 200
}'
返回的 tool_calls 可直接传给工具函数,结果再喂回模型,完整支持多轮工具调用。
视觉多模态通过 --mmproj 加载投影权重,API使用标准的 image_url 格式,每张图片分配1024个min token。
最后的三条铁律
- 不要开 CUDA Graph(GGML_CUDA_GRAPH_OPT=1)——混合架构下KV cache变化频繁,重录成本太高,多轮对话直接掉到10t/s。
- 客户端必须限制 max_tokens——服务端的
--n-predict不是硬限制,某些客户端如DeerFlow如果不设上限,模型会无限生成,导致单请求卡死整个槽位。 - 64K是单卡甜点——128K在24GB上会触发虚拟内存瓶颈,速度降到6t/s以下。如果非得跑长上下文,建议用35B MoE + SGLang(但需要两张卡)。
一张游戏卡跑出60t/s性能、多轮对话不掉速、支持工具调用和多模态——Qwen3.6-27B + llama.cpp的组合让我彻底告别了云端API。你手头的单卡部署,踩过什么别致的坑?欢迎来交流。
