共计 3488 个字符,预计需要花费 9 分钟才能阅读完成。
最近公司安全审计策略收紧,原本绑在 VS Code 上的云端 Copilot 和 Cursor 订阅全被拉了黑名单——代码严禁出公网。对于习惯了 AI 自动补全的键盘侠来说,这无异于直接收走了机械键盘。折腾病当场发作:既然不让上云,那就把 Coding Agent 搬回局域网。
我的目标很激进:单张消费级 RTX 4090(24G 显存),跑当前开源代码天花板 Qwen2.5-Coder-32B,同时保证补全(FIM)首字延迟低于 250ms,且支持 16k 以上的上下文窗口。市面上大部分教程教你用 Ollama 跑个 7B 或 14B 模型,但在真实业务项目里,小参数模型写写独立函数还行,一遇到跨文件逻辑和复杂的 TypeScript 类型推导就满嘴胡话。要用就直接上 32B。
一、方案选型:为什么弃用 Ollama 转向 vLLM?
在立项阶段,我对比了两套技术路线:
- Ollama (llama.cpp 体系): 优点是跨平台、开箱即用、显存不够还能自动把部分层卸载(Offload)到 CPU 内存。但我实测下来,llama.cpp 在并发请求和连续代码补全(Tab 触发)时,上下文切换严重拉胯,而且对长上下文的前缀缓存(Prefix Caching)支持不够极致,代码补全时频繁重新计算 Prefill,卡顿感明显。
- vLLM 体系: 原生针对吞吐量和延迟设计,支持 PagedAttention、连续批处理(Continuous Batching)以及开箱即用的 Chunked Prefill。特别是在搭配 AWQ 量化格式时,推理吞吐量是 Ollama 的 2.5 到 4 倍。
显存计算账本很透明:Qwen2.5-Coder-32B 的 FP16 原生权重需要 64GB 显存,单卡 4090 根本吃不下。经过实测评估,INT4-AWQ 量化版本 是平衡推理质量与显存占用的黄金分割点——模型静态权重占用约为 18.5 GB,剩下的 5.5 GB 显存全部留给 KV Cache,配合合理的分块策略,刚好能支撑 16,384 tokens 的上下文。
二、Docker + vLLM 生产级部署实战
避免污染宿主机的 CUDA 环境,直接用官方 Docker 镜像拉起。以下是我调优后的 docker-compose.yml 生产配置:
version: '3.8'
services:
vllm-qwen:
image: vllm/vllm-openai:v0.6.3.post1
container_name: vllm-coder-32b
runtime: nvidia
restart: always
environment:
- CUDA_VISIBLE_DEVICES=0
- HF_ENDPOINT=https://hf-mirror.com # 国内镜像加速
volumes:
- /home/geek/models:/root/.cache/huggingface
ports:
- "8000:8000"
ipc: host
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
command: >
--model Qwen/Qwen2.5-Coder-32B-Instruct-AWQ
--quantization awq
--dtype half
--max-model-len 16384
--gpu-memory-utilization 0.94
--enforce-eager
--enable-prefix-caching
--max-num-seqs 16
--trust-remote-code
--port 8000
核心参数避坑解析:
--gpu-memory-utilization 0.94:默认的 0.90 会浪费近 1GB 显存。设为 0.94 可以给 KV Cache 挤出宝贵的空间。但 千万不要设成 0.98,PyTorch 运行时的临时激活值(Activation)瞬间峰值会直接触发 CUDA OOM 崩溃。--enforce-eager:重点踩坑点! 很多教程推荐开启 CUDA Graph(不加此参数),但在 24G 极限显存压榨下,CUDA Graph 预编译捕获会额外吃掉 1~1.5GB 的显存缓冲区,直接挤压 KV Cache 导致长文本暴毙。加--enforce-eager牺牲大约 5% 的峰值吞吐,换取绝对的显存稳定性。--enable-prefix-caching:必须开启! 代码场景下,系统 Prompt、跨文件引入定义往往是重复的。开启后,相同前缀的 Token 不需要重新计算 Prefill,代码补全响应直接起飞。--ipc: host:防止 PyTorch 共享内存在 Docker 默认的 64MB 限制下发生死锁。
三、VS Code 客户端接入(Continue 插件)
服务端容器跑起来后,先用 curl 测一下连通性:
curl http://localhost:8000/v1/models \
-H "Content-Type: application/json"
确认返回 Qwen2.5-Coder-32B-Instruct-AWQ 后,在本地 VS Code 安装 Continue 插件。打开 ~/.continue/config.json,按如下配置(把 IP 替换为你的服务器局域网 IP):
{
"models": [
{"title": "Qwen2.5-Coder-32B-AWQ (Local)",
"provider": "openai",
"model": "Qwen/Qwen2.5-Coder-32B-Instruct-AWQ",
"apiBase": "http://192.168.1.100:8000/v1",
"apiKey": "EMPTY",
"contextLength": 16384
}
],
"tabAutocompleteModel": {
"title": "Qwen2.5-Coder-32B-Tab",
"provider": "openai",
"model": "Qwen/Qwen2.5-Coder-32B-Instruct-AWQ",
"apiBase": "http://192.168.1.100:8000/v1",
"apiKey": "EMPTY"
},
"tabAutocompleteOptions": {
"debounceDelay": 200,
"maxPromptTokens": 4096
}
}
进阶技巧:如果你发现单卡 4090 在同时跑 Chat 交互和行内代码补全(Tab 键)时有抢占现象,建议将 tabAutocompleteModel 改为在本地直接跑一个小体量的 Qwen2.5-Coder-1.5B(随便丢在 CPU 或核显上跑),而 32B 大模型专供复杂逻辑架构、代码重构与 Bug 定位。
四、实测性能与压测数据
在局域网内使用 Python 脚本并发模拟请求,调用 vLLM 提供的 OpenAI 兼容接口,实测表现如下:
| 测试场景 | Context 长度 | 首字延迟 (TTFT) | 生成速度 (TPS) | 显存占用 |
|---|---|---|---|---|
| 单行行内补全 (Tab) | 1,024 tokens | 142 ms | 58 tokens/s | 21.8 GB |
| 中型函数重构 | 4,096 tokens | 210 ms | 54 tokens/s | 22.4 GB |
| 全项目上下文分析 | 14,500 tokens | 680 ms | 47 tokens/s | 23.2 GB |
得益于 --enable-prefix-caching,在同一个代码文件里持续敲代码时,首字延迟几乎稳定在 150ms 左右,主观体感已经无限接近原生 GitHub Copilot。
五、实操踩坑与极限调优总结
- 别碰 GGUF 的高并发: 最初我尝试过在 Ollama 下加载 Qwen-32B 的 Q4_K_M,并发线程一上来,CPU 和 GPU 交换直接把 PCIe 4.0 通道挤爆,首字延迟瞬间飙到 2 秒以上。生产力工具必须认准 vLLM 的 PagedAttention。
- 警惕上下文爆炸: 32B 模型的 KV Cache 消耗极其敏感。不要盲目把
--max-model-len拉到 32768。在 24G 显存下,超过 16384 token 只要并发达到 2,有概率引发显存碎片溢出。16k 上下文应对单次几千行的项目文件分析已经游刃有余。 - 宿主机散热必须做好: 连续跑大段代码重构时,4090 长期处于 350W~400W 满载工况,务必在
nvidia-smi中把功耗墙限制在 300W(sudo nvidia-smi -pl 300)。实测性能损失不到 3%,但显存温度和风扇噪音能下降一个数量级,避免长时间高负荷掉驱动。
这套方案目前在局域网稳定运行了两个多月,不仅彻底摆脱了云端审计的合规风险,最关键的是把“完全属于自己的代码智能大脑”攥在手里的踏实感——这才是极客折腾的最大乐趣。
===END===