共计 7846 个字符,预计需要花费 20 分钟才能阅读完成。
最近把手头这台插了两张二手 RTX 3090(总计 48GB 显存)的工作站重新折腾了一遍,目标很明确:跑通 DeepSeek-R1-Distill-Qwen-32B,并接入内部的代码审查 Agent 和日常知识库。最开始图省事直接用了 Ollama,单人对话还行,但只要脚本一跑并发请求,吞吐量直接掉底,甚至偶发假死。折腾了两天切换到 vLLM 并做完参数调优后,并发吞吐翻了近 4 倍。这篇文章把完整的部署配置、显存精算以及几个极具迷惑性的深坑记录下来,供各位在本地搭 AI 算力节点时参考。
一、核心背景与痛点拆解:为什么弃用 Ollama 转向 vLLM?
32B 参数量的模型,如果直接加载 FP16 半精度原版权重,光模型本身就要吃掉约 64GB 显存,双卡 3090 根本塞不下。因此,4-bit 量化(AWQ 或 GPTQ)是唯一兼顾智商和显存的方案。
在早期测试中,我用 Ollama 跑 Q4_K_M 格式的 GGUF 模型,遇到了三个绕不过去的痛点:
- 并发调度弱:虽然新版 Ollama 支持了并发参数(
OLLAMA_NUM_PARALLEL),但在多路长文本 Prompt 同时涌入时,缺乏高效的 Continuous Batching(连续批处理)机制,队列等待时间极长。 - 长上下文显存爆炸:R1 这类推理模型自带极长的思维链(
<think>...</think>),动辄输出两三千 Token。GGUF 在多并发下 KV Cache 膨胀极快,稍不留神就触发 PCIe 内存卸载,推理速度瞬间从 35 tokens/s 暴跌到 3 tokens/s。 - 双卡通信效率低:双卡 3090 没有 NVLink 桥接器(穷人版主板槽位间距不对),走主板 PCIe 4.0 x8 通信时,vLLM 的张量并行(Tensor Parallelism)配合 PagedAttention 明显比 llama.cpp 默认的层切分(Layer Split)延迟更低。
二、环境准备与显存账本精算
在动手敲命令之前,先算清楚 48GB 显存怎么分。我选用的模型是社区验证过精度损失极小的 DeepSeek-R1-Distill-Qwen-32B-AWQ:
- 模型权重占用:32B × 0.5 Bytes (INT4) ≈ 16GB ~ 18.5GB(包含量化缩放因子与 Embedding 层开销),平摊到两张卡上,每张卡占约 9.2GB。
- CUDA Context 与 PyTorch 预留:每张卡固定吃掉 1.5GB ~ 2GB。
- 剩余可用 KV Cache 空间:单卡 24GB – 9.2GB – 2GB ≈ 12.8GB,双卡合计约 25GB 专门用来跑上下文缓存!这足以支撑 32K 上下文窗口下的多路并发。
1. 宿主机驱动与通信检查
在双卡无 NVLink 的 Linux 机器(我用的是 Ubuntu 22.04 LTS)上,先看一眼拓扑结构,确认两张卡是不是挂在同一个 CPU Root Complex 下:
nvidia-smi topo -m
如果输出里 GPU0 和 GPU1 之间显示 NODE 或 PIX,说明走的是正常 PCIe 通道;如果显示 SYS(跨 NUMA 节点走 QPI/UPI 总线),后面在 Docker 环境变量里 必须关闭 NCCL P2P,否则容器启动加载权重时会直接卡死无报错!这是我踩的第一个大坑。
2. 提前拉取模型权重
千万别让 vLLM 容器在启动时自己去连 HuggingFace 下载,断线重连能让人崩溃。直接在宿主机用 hf-transfer 或魔搭社区(ModelScope)拉到本地高速 NVMe 固态盘:
pip install -U huggingface_hub hf_transfer
export HF_ENDPOINT=https://hf-mirror.com
export HF_HUB_ENABLE_HF_TRANSFER=1
huggingface-cli download casperhansen/deepseek-r1-distill-qwen-32b-awq \
--local-dir /data/models/deepseek-r1-32b-awq
三、关键配置与实战避坑
1. 生产级 Docker Compose 编排
相比于在宿主机直接配 Conda 环境被 CUDA 版本折磨,官方镜像是最稳妥的。以下是经过我压测微调后的 docker-compose.yml:
version: '3.8'
services:
vllm-deepseek:
image: vllm/vllm-openai:v0.7.2
container_name: vllm-r1-32b
restart: unless-stopped
runtime: nvidia
# 关键点 1:多卡张量并行必须给足共享内存,默认 64MB 必崩
shm_size: '16gb'
ports:
- "8000:8000"
volumes:
- /data/models/deepseek-r1-32b-awq:/model:ro
environment:
- NVIDIA_VISIBLE_DEVICES=0,1
# 关键点 2:双卡消费级显卡无 NVLink 且跨槽位时,开启此项防卡死
- NCCL_P2P_DISABLE=1
- NCCL_IB_DISABLE=1
- VLLM_WORKER_MULTIPROC_METHOD=spawn
command: >
--model /model
--served-model-name deepseek-r1-32b
--quantization awq_marlin
--dtype half
--tensor-parallel-size 2
--gpu-memory-utilization 0.90
--max-model-len 32768
--max-num-seqs 16
--enable-prefix-caching
--enable-reasoning
--reasoning-parser deepseek_r1
--api-key sk-nassky-internal-token
2. 核心启动参数深度解析(全是血泪教训)
--quantization awq_marlin:性能起飞的核心!不要只写awq。对于 Ampere 架构(RTX 30 系列)及更新的显卡,强制指定 Marlin 内核重排权重,计算效率比原生 AWQ 内核高出 30% 以上。--max-model-len 32768:Qwen2.5 底座默认支持 128K 上下文。如果不手动限制这个值,vLLM 启动自检时会尝试按 128K 去预分配显存块,直接报 OOM 退出。对于日常 Coding 和 RAG 任务,32K 绰绰有余。--enable-prefix-caching:强烈建议开启(自动前缀缓存)。当你用 Agent 反复发送带有相同 System Prompt 或同一份代码仓库上下文的请求时,首字延迟(TTFT)能从 1.8 秒直接降到 0.2 秒以内。--enable-reasoning --reasoning-parser deepseek_r1:vLLM v0.7.1 之后新增的神仙特性。它会自动把模型输出的<think>标签解析并剥离到 OpenAI API 规范的reasoning_content字段里,前端应用(如 NextChat、Dify、Cursor)直接原生渲染思考折叠框,不用自己在中间层写正则去切分流式文本了。
3. Nginx 反向代理流式断流排障
服务跑起来后,我通过内网的 Nginx 反代暴露 HTTPS 接口。结果在跑复杂算法题时,DeepSeek 思考了大概 65 秒还没吐完思维链,客户端突然报错断开。查 Nginx 错误日志发现是经典的 upstream timed out 以及缓冲区截断。
给大模型配反代,绝对不能套用常规 Web 接口的模板,必须在对应的 location 块里加上这套防断流组合拳:
location /v1/ {
proxy_pass http://127.0.0.1:8000/v1/;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# 核心:彻底关闭代理缓冲,确保 SSE (Server-Sent Events) 逐字丝滑吐出
proxy_buffering off;
proxy_cache off;
chunked_transfer_encoding on;
# 推理模型输出极长,超时时间必须拉长到 10 分钟以上
proxy_read_timeout 600s;
proxy_send_timeout 600s;
}
四、真实压测性能对比与 Python 验证
为了量化调优前后的差距,我写了个异步并发压测脚本(模拟 8 个并发请求,每个请求输入约 1500 Tokens===TITLE===
本地大模型服务化终极指南:用 One-API 统一你的 Ollama 与云端 AI
===EXCERPT===
还在为同时调用本地 Ollama、Llamafile 和远程 OpenAI API 而手写一堆判断逻辑吗?本文 的代码片段要求找 Bug,输出约 1200 Tokens)。硬件环境锁定在双卡 RTX 3090(功耗墙限制在 300W/ 卡,防止机箱积热降频)。
实测对比数据如下:
| 推理后端与量化方案 | 单 Compose 快速启动开始,手把手教你如何配置,将各类本地和云端大模型统一为并发生成速度 (t/s) | 8 并发总吞吐量 (t/s) | 8 OpenAI 兼容的接口。内容涵盖多模型负载均衡、统一密钥管理、性能压测心得以及关键的 Docker 网络并发平均首字延迟 (TTFT) | 显存占用 (双卡合计) |
|---|---|---|---|---|
| Ollama (GGUF Q4_K_M) | 36 避坑技巧,助你搭建起一个生产级的本地 AI 服务中枢。 ===TAGS=== LLM, One.4 tokens/s |
68.2 tokens/s | 4.85 s-API, Ollama, 本地部署, AI 代理 ===CONTENT=== 最近在折腾本地大 |
26.4 GB (动态波动) |
| vLLM (原生 AWQ 内核) | 39.1 tokens/s | 195. 模型,笔记本上的 Ollama 跑着 Llama3,服务器上还有个 Llamafile 部署的 Q6 tokens/s | 0.92 s | 43.2 GB (预分配 wen,偶尔还得调用下 OpenAI 的 GPT-4。结果就是,我的 Python 代码里充斥着大量的 锁定) |
| vLLM (AWQ_Marlin + Prefix Cache) | 47.8 tokens/s | 264.5 tokens/s | 0.35 s (命中缓存) | 43.if/else,每个模型的 Client、API Key 和 Base URL 都不一样,混乱得像一团麻 2 GB (预分配锁定) |
压测实。每次想切换个模型做测试,都得改动代码,这种“手工作坊”模式显然无法持续测下来发现,单人单线程聊天时 Ollama 和 vLLM 体感差距不算悬殊(36 vs 47。
这正是许多独立开发者和小型团队在探索 AI 应用时会遇到的典型痛点:t/s,都远超人类阅读速度);但一旦上升到 Agent 工作流的多分支并发调用,vLLM 模型资源分散,接口标准不一,管理成本极高。我们需要一个“交通枢纽”,一个能统一调度配合 Marlin 内核直接拉开了近 4 倍的总吞吐差距,且全程显卡利用率稳稳咬死所有 AI 模型的网关。经过一番调研和实测,我找到了一个近乎完美的开源解决方案 —— 在 95% 以上。
最后贴一段客户端调用验证代码,测试 reasoning_content 是否One-API。它就像一个瑞士军刀,轻量、高效,能帮你把杂乱无章的被正确剥离:
from openai import OpenAI
client = OpenAI(base_url="http://127.0.0.1:8000/v AI 调用变得井然有序。
一、为什么需要 One-API?痛点拆解
1",
api_key="sk-nassky-internal-token"
)
stream = client.chat.completions.create(
model="deepseek-r1-32b",
messages=[在直接上手前,我们先明确一下,为什么要给自己增加一个“中间层”。直接调用{"role": "user", "content": "用 Python 写一个线程安全的 LRU 缓存,简要说明锁的粒度。"}],
stream=True Ollama 的 API (http://localhost:11434/api/generate) 不香吗?在,
temperature=0.6 # R1 官方建议温度控制在 0.5~0.7 之间个人测试时当然可以,但一旦你的应用需要接入多个模型,或者有多个客户端使用时,问题就,切勿设为 0!)
print("=== 思考过程 ===")
in_content = False
for chunk in stream: 暴露出来了:
- 接口不统一: Ollama、Groq、Together
delta = chunk.choices[0].delta
# 打印思维链
if hasattr(delta, "reasoning_content") and delta.reasoning_content:
print(delta.reasoning_content, end="", flush=True)
# 打印正式回答
elif delta.content:
if not in_content.ai、OpenAI 的 API 格式、认证方式各不相同,你需要为每一种都写一套适配逻辑:
print("\n\n=== 正式回答 ===")
in_content = True
print(delta.content, end="", flush=True)
这里顺便提个细节:调用 DeepSeek-R1。
temperature 设为 0.0,也不要塞冗长的 System Prompt,否则极易触发思维链无限复读机(Endless GPU,部署了多个相同的本地模型实例,如何实现请求的负载均衡?如何在一个模型不可用时自动切换到备用模型?temperature=0.6,把所有指令直接放在 User Prompt 里效果最好。
总结与避坑心得
折腾本地大模型基础设施,本质上是在显存带宽、计算精度和上下文模型、各个渠道的调用次数和 Token 消耗,成本控制无从谈起。
切换成本高:
今天想用 Llama3,明天想换 Claude3,客户端代码必须跟着改。理想状态下,客户端应该完全无感知后端的模型变化。One-API 正是
- 选型看场景:如果是 Mac Studio(统一内存架构)或者单卡笔记本偶尔为解决以上所有问题而生的。它是一个使用 Go 语言编写的高性能 API 代理,核心功能就是将所有问答,老老实实用 Ollama / LM Studio 最省心,因为 vLLM 会一次性霸占 90% 的显不同来源的 AI 模型 API 封装成统一的、与 OpenAI API 完全兼容的接口。这意味着,你生态存不释放,影响你打游戏或跑其他模型;如果是 Linux 独立算力机长期挂载服务,无中所有的应用,只需要使用标准的 OpenAI SDK,就能调用你接入的任何模型。
二、关键配置脑上 vLLM 或 SGLang。
- 关注硬件散热与电源瞬态:双卡 3090 与实战部署
废话不多说,我们直接开干。我个人推荐使用 Docker- 在跑 Marlin 算子高并发时,瞬时功耗尖峰非常猛,老旧的 850W 电源 Compose 进行部署,这是最省心、最利于迁移和维护的方式。
第一步:使用 Docker-Compose 快速启动
在你的服务器或本地机器上,创建一个目录(例如 很容易直接触发过流保护重启机器。如果电源不够顶(建议 1200W 起步),务必在宿主机跑一行
sudo nvidia-smi -pl 280锁一下功耗墙,算力只掉不到~/one-api-data),然后在该目录下新建一个docker-compose.yml文件,内容如下:version: '3.8' 5%,但机器稳如老狗。 - KV Cache 量化留作后手:如果后续 services:
one-api:
image: justsong/one-api:latest
container_name: 业务需要把上下文强行扩到 64K 甚至更高,可以在 vLLM 启动参数中加上
--kv-cache- one-api restart: always ports: - "3000:3000" volumes: - ./data:/data environment: - TZ=Asia/Shanghai dtype fp8(需要驱动和特定架构配合,30 系部分支持受限,40 系完美这个配置非常简洁:
image: justsong 支持),能在几乎不掉点的情况下再省出一半的缓存空间。
/one-api:latest:使用官方最新镜像。
ports: - "3000:3000":将容器的 3000 端口映射到宿主机的 3000 端口。你可以根据需要修改宿主机端口。volumes: - ./data:/data:将容器内的 /data 目录(用于存放 SQLite 数据库文件)挂载到当前目录下的 data 文件夹,确保数据持久化。在 docker-compose.yml 所在目录,执行以下命令启动服务:
docker-compose up -d
稍等片刻,服务就跑起来了。现在通过浏览器访问 http:// 你的 IP 地址:3000,就能看到 One-API 的登录界面。
第二步:初始化与渠道配置
首次访问会引导你创建管理员账号。完成后,登录进入主界面,我们要做的核心工作就是配置「渠道」。
点击左侧菜单的「渠道」,然后「添加新的渠道」。
配置本地 Ollama 渠道
这是本文的重点。假设你的 Ollama 服务运行在另一台 IP 为 192.168.1.100 的机器上,端口为 11434。
- 类型: 选择
Ollama。 - 名称: 自定义,例如
My-Ollama-Llama3。 - 基础 URL: 填入
http://192.168.1.100:11434。 - 模型: 填入你在该 Ollama 服务上拥有的模型,例如
llama3:8b,qwen:7b,用逗号分隔。
🚨 避坑指南: 如果你的 One-API 和 Ollama 都以 Docker 容器的形式运行在同一台机器上,这里的「基础 URL」是个大坑。你不能填 http://localhost:11434 或 http://127.0.0.1:11434,因为这指向的是 One-API 容器内部,而不是宿主机。正确的做法是:
- Linux: 使用宿主机的局域网 IP,例如
http://192.168.1.100:11434。 - Mac/Windows (Docker Desktop): 可以使用特殊的 DNS 名称
http://host.docker.internal:11434,它会解析到宿主机。
配置 OpenAI 渠道
如果你也需要调用 OpenAI 的服务,配置起来更简单:
- 类型: 选择
OpenAI。 - 名称: 例如
GPT-4-Official。 - 密钥: 填入你的 OpenAI API Key (
sk-...)。 - 模型: 填入
gpt-4-turbo,gpt-3.5-turbo。
配置完成后,你可以点击「测试」按钮,如果显示绿色数值(响应时间),说明配置成功。
第三步:创建令牌并统一调用
渠道配置好了,相当于我们把后端的“供应商”都接进来了。现在需要生成一个统一的“门禁卡”(令牌),让我们的应用程序使用。
点击左侧菜单「令牌」,