ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Ollama vs vLLM:本地大模型部署选型与高并发推理实践

Ollama vs vLLM:本地大模型部署选型与高并发推理实践

1. 先搞清楚这两个东西到底在解决什么问题

1.1 从一行命令到千级并发,中间差了什么

很多人第一次接触本地大模型部署,都是从 Ollama 开始的。装完之后敲一行ollama run qwen2.5,模型就跑起来了,浏览器里打开个界面就能对话,体验确实丝滑。但当你把这个东西丢到生产环境,让几十上百个用户同时用的时候,问题就来了——请求排队、响应变慢、显存爆掉、进程直接挂掉。

这不是 Ollama 的错,它的定位本来就是“让个人开发者快速跑起来一个模型”。而 vLLM 从诞生第一天起,瞄准的就是“怎么让一块 GPU 同时服务尽可能多的并发请求”。两者的设计目标完全不同,所以选型的时候不能只看“哪个装起来方便”,得看你到底要拿它干什么。

我见过太多团队在这个问题上踩坑:开发阶段用 Ollama 跑得好好的,上线之后用户一多就崩,然后紧急换 vLLM,结果发现部署方式、接口格式、模型管理全都不一样,又得重新折腾一遍。所以这篇文章的核心目的,就是帮你在动手之前就想清楚——你到底该用哪个,以及为什么。

1.2 用生活化类比理解两者的本质差异

打个比方。Ollama 就像你家楼下那家小餐馆,老板一个人掌勺,你点一个菜他做一个,味道不错,上菜也快。但如果同时来了二十桌客人,老板就忙不过来了,只能一桌一桌排队做。

vLLM 更像是一个中央厨房,它有一套高效的调度系统,能把多个订单里的相似工序合并处理,同时出餐。它的核心武器叫PagedAttention,这个技术后面会详细讲,简单说就是让显存的利用率大幅提升,从而在同一块显卡上同时处理更多请求。

所以选择逻辑很清晰:如果你是自己学习、做 Demo、小团队内部用,Ollama 足够了;如果你要对外提供服务、要扛并发、要控制成本,vLLM 是更合适的选择。但实际情况往往比这复杂,下面我会从多个维度展开拆解。

1.3 本文适合哪些人看

这篇文章主要面向三类读者:第一类是有一定开发经验、想在本地或服务器上部署大模型但还没决定用哪个工具的工程师;第二类是已经在用 Ollama 或 vLLM,但遇到了性能瓶颈或者想优化现有方案的开发者;第三类是做技术选型的技术负责人,需要一份清晰的对比来支撑决策。

不管你是哪种,我都会尽量把原理讲透、把操作步骤写清楚、把踩过的坑标出来。不会只告诉你“用这个就对了”,而是告诉你“为什么用这个、什么情况下不该用这个”。

2. 核心架构差异:为什么 vLLM 能扛并发而 Ollama 不能

2.1 Ollama 的架构:简单直接但天花板明显

Ollama 底层用的是 llama.cpp 作为推理后端,llama.cpp 本身是一个非常优秀的项目,它用 C++ 实现了高效的 CPU 和 GPU 推理,支持量化、支持多种模型格式。Ollama 在它上面包了一层,提供了模型管理、API 接口、命令行交互等功能。

它的请求处理模型是相对简单的:来一个请求,分配资源,跑完释放。虽然新版本也做了一些并发优化,比如支持多个请求排队、支持一定的批处理,但它的核心调度逻辑并不是为高并发设计的。当并发请求数超过一定阈值时,你会明显感觉到响应时间线性增长,甚至出现请求超时。

具体来说,Ollama 默认的并发数(OLLAMA_NUM_PARALLEL)通常设置为 1 或很小的值。你可以手动调大这个参数,但调大之后显存占用会迅速上升,因为每个并发请求都需要独立的 KV Cache 空间。而 llama.cpp 的 KV Cache 管理方式比较“朴素”,不像 vLLM 那样做了精细的分页管理,所以显存浪费比较严重。

2.2 vLLM 的杀手锏:PagedAttention 到底强在哪

vLLM 最核心的技术创新就是PagedAttention。要理解它,先得知道传统推理中 KV Cache 是怎么管理的。

在 Transformer 模型推理过程中,每生成一个 token,都需要缓存之前所有 token 的 Key 和 Value 向量,这就是 KV Cache。传统做法是给每个请求预分配一块连续的显存空间,大小按照最大可能的序列长度来算。问题是,实际生成的序列长度往往远小于最大值,这就导致了大量的显存浪费——业内通常估计浪费在 60% 到 80% 之间。

PagedAttention 的思路借鉴了操作系统的虚拟内存分页机制。它把 KV Cache 切分成固定大小的“页”(block),不需要连续存储,而是通过一张映射表来管理。这样一来,显存利用率可以提升到 90% 以上,同样一块显卡能同时服务的请求数就大幅增加了。

实测数据:在相同硬件条件下,vLLM 的吞吐量通常是 Ollama 的 5 到 20 倍,具体倍数取决于模型大小、序列长度和并发数。这个差距在并发数越高的时候越明显。

2.3 连续批处理:另一个关键差异

除了 PagedAttention,vLLM 还实现了连续批处理(Continuous Batching)。传统的静态批处理是这样的:攒一批请求,一起送进模型,等这批全部跑完再处理下一批。问题是,同一批里有的请求可能只需要生成 10 个 token,有的需要生成 500 个,那短的就得等长的跑完,GPU 利用率很低。

连续批处理的做法是:不等整批跑完,只要有请求完成了,就立刻把新的请求塞进去。这样 GPU 几乎一直在满负荷工作,不会出现“等尾巴”的情况。这个机制对于在线服务场景特别重要,因为用户的请求是随时到来的,不可能攒批。

Ollama 在这方面的支持相对有限。虽然 llama.cpp 也在逐步引入一些批处理优化,但整体调度策略和 vLLM 不在一个层面上。

2.4 架构差异带来的实际影响对照

对比维度OllamavLLM
推理后端llama.cpp自研(基于 PyTorch)
KV Cache 管理连续分配,浪费较多PagedAttention,利用率 90%+
批处理方式静态批处理为主连续批处理
并发能力个位数到十位数百位数到千位数
显存效率一般优秀
部署复杂度极低中等
模型格式支持GGUF 为主HuggingFace 格式为主
量化支持丰富(Q4/Q5/Q8 等)支持 AWQ/GPTQ/FP8 等
适合场景个人开发、小规模生产环境、高并发

这张表不是要分出谁好谁坏,而是帮你快速定位自己的需求落在哪个区间。接下来我会从实操角度,分别讲清楚两者的部署和使用细节。

3. Ollama 实操:从安装到调优的完整路径

3.1 安装环节的坑与技巧

Ollama 的安装本身不复杂,官网下载安装包双击就行。但国内用户经常会遇到下载慢的问题,这个没什么好办法,要么耐心等,要么找国内的镜像源。Linux 下可以用官方的一键脚本:

curl -fsSL https://ollama.com/install.sh | sh

安装完成后,Ollama 默认会把模型存在系统盘的用户目录下。如果你系统盘空间不大,这个很快就会撑满。解决办法是设置环境变量OLLAMA_MODELS指向一个大容量磁盘:

export OLLAMA_MODELS=/data/ollama/models

Windows 下则是在系统环境变量里添加这个变量,然后重启 Ollama 服务。这个操作看起来简单,但很多人是等到系统盘红了才想起来改,那时候迁移模型又得折腾一遍。

另一个常见问题是 Ollama 默认开机自启,有些用户不想让它一直占着显存。Linux 下可以用systemctl disable ollama关掉自启,Windows 下在任务管理器启动项里禁用就行。

3.2 模型拉取与量化选择

Ollama 的模型库(ollama.com/library)提供了大量预量化好的模型,直接ollama pull就能用。量化的选择很关键,它直接决定了显存占用和输出质量之间的平衡。

以 7B 模型为例,不同量化级别的显存占用大致如下:

量化级别显存占用(7B)质量损失推荐场景
Q8_0约 8GB极小显存充足,追求质量
Q5_K_M约 5.5GB很小平衡之选
Q4_K_M约 4.5GB较小显存有限,日常使用
Q4_0约 4GB可感知显存紧张
Q3_K_M约 3.5GB明显不推荐,除非实在没空间
Q2_K约 3GB严重基本不可用

我的经验是,Q4_K_M 是大多数场景下的甜点级别。它在 7B 模型上只需要 4.5GB 左右的显存,输出质量相比 FP16 的损失在日常对话和文本处理任务中几乎感觉不到。如果你用的是 14B 或更大的模型,Q4_K_M 同样适用,显存占用大约翻倍。

3.3 并发参数调优:OLLAMA_NUM_PARALLEL 怎么设

Ollama 有一个关键环境变量OLLAMA_NUM_PARALLEL,控制同时处理多少个请求。默认值通常是 1,意味着请求是串行处理的。你可以把它调大,比如设为 4:

export OLLAMA_NUM_PARALLEL=4

但这里有个陷阱:每增加一个并发,就需要额外的 KV Cache 空间。如果你的显存本来就紧张,调大这个参数会导致 OOM(显存溢出)。所以调之前先算一下:模型本身占多少显存,每个并发请求的 KV Cache 占多少,加起来不能超过显卡容量。

KV Cache 的大小可以用这个公式估算:

KV Cache 大小 = 2 × 层数 × 注意力头数 × 头维度 × 序列长度 × 数据类型字节数

以 Llama 3 8B 为例,32 层,32 个注意力头,头维度 128,序列长度 4096,FP16 数据类型(2 字节):

2 × 32 × 32 × 128 × 4096 × 2 = 约 2GB

也就是说每个并发请求大约需要 2GB 的 KV Cache。如果你的显卡是 24GB,模型本身占了 5GB,那理论上最多支持 9 个并发左右,但还要留一些余量给其他开销,实际设 4 到 6 比较稳妥。

3.4 API 接口与外部集成

Ollama 默认在11434端口提供 REST API,接口格式兼容 OpenAI 的部分规范,所以很多工具可以直接对接。比如用 FastAPI 调用:

import httpx async def chat(prompt: str): async with httpx.AsyncClient() as client: resp = await client.post( "http://localhost:11434/api/generate", json={ "model": "qwen2.5:7b", "prompt": prompt, "stream": False }, timeout=60.0 ) return resp.json()["response"]

如果你要把它接入 Dify、FastGPT 这类平台,通常只需要填上 Ollama 的地址和模型名就行。但要注意,这些平台如果有较高的并发需求,Ollama 很可能扛不住,后面会讲怎么判断和迁移。

4. vLLM 实操:从零搭建一个能扛并发的推理服务

4.1 安装 vLLM 前必须注意的依赖问题

vLLM 的安装比 Ollama 复杂得多,最大的坑是它会改变你已经安装好的 PyTorch 版本。vLLM 对 PyTorch、CUDA、cuDNN 的版本要求非常严格,pip 安装的时候它会自动拉取它需要的版本,可能把你环境里原来的 torch 覆盖掉。

如果你机器上还有其他项目依赖特定版本的 PyTorch,强烈建议用虚拟环境隔离:

python -m venv vllm-env source vllm-env/bin/activate pip install vllm

如果要用特定 CUDA 版本,可以指定:

pip install vllm --extra-index-url https://download.pytorch.org/whl/cu121

安装完成后验证一下:

python -c "import vllm; print(vllm.__version__)"

注意:vLLM 目前对 Windows 的原生支持有限,官方推荐在 Linux 下运行。Windows 用户可以考虑用 WSL2,或者通过 Docker 部署。如果非要在 Windows 下跑,可以试试 GPUStack 这类工具来做封装。

4.2 启动一个 OpenAI 兼容的推理服务

vLLM 最常用的启动方式是通过命令行:

python -m vllm.entrypoints.openai.api_server \ --model Qwen/Qwen2.5-7B-Instruct \ --host 0.0.0.0 \ --port 8000 \ --tensor-parallel-size 1 \ --max-model-len 4096 \ --gpu-memory-utilization 0.9

几个关键参数解释一下:

  • --tensor-parallel-size:张量并行数,如果你有多张显卡,设为显卡数量可以分摊模型。
  • --max-model-len:最大序列长度,设得越大 KV Cache 占用越多,根据实际需求设。
  • --gpu-memory-utilization:GPU 显存利用率上限,默认 0.9,意思是最多用 90% 的显存。留 10% 是给一些临时开销用的,不建议设成 1.0。

启动之后,它就提供了一个和 OpenAI API 兼容的接口,可以直接用 OpenAI 的 SDK 调用:

from openai import OpenAI client = OpenAI(base_url="http://localhost:8000/v1", api_key="dummy") resp = client.chat.completions.create( model="Qwen/Qwen2.5-7B-Instruct", messages=[{"role": "user", "content": "你好"}] ) print(resp.choices[0].message.content)

4.3 并发压测:怎么知道你的服务能扛多少

部署完了不代表就完事了,你得知道它能扛多少并发。我一般用wrk或者locust来做压测。简单一点可以用 Python 写个脚本:

import asyncio import aiohttp import time async def send_request(session, i): start = time.time() async with session.post( "http://localhost:8000/v1/chat/completions", json={ "model": "Qwen/Qwen2.5-7B-Instruct", "messages": [{"role": "user", "content": f"测试请求 {i}"}], "max_tokens": 100 } ) as resp: await resp.json() return time.time() - start async def main(): concurrency = 50 async with aiohttp.ClientSession() as session: tasks = [send_request(session, i) for i in range(concurrency)] latencies = await asyncio.gather(*tasks) print(f"并发数: {concurrency}") print(f"平均延迟: {sum(latencies)/len(latencies):.2f}s") print(f"最大延迟: {max(latencies):.2f}s") asyncio.run(main())

逐步增加并发数,观察延迟变化。当延迟开始急剧上升的时候,说明已经接近瓶颈了。记录下这个拐点,就是你这套配置的安全并发上限。

4.4 显存不够怎么办:量化与多卡方案

vLLM 支持多种量化方案,最常用的是 AWQ 和 GPTQ。以 AWQ 为例,启动时加上量化参数:

python -m vllm.entrypoints.openai.api_server \ --model Qwen/Qwen2.5-7B-Instruct-AWQ \ --quantization awq \ --max-model-len 4096

AWQ 的 4bit 量化可以把 7B 模型的显存占用从约 14GB 降到约 5GB,代价是轻微的质量损失。实测下来,在大多数对话和文本任务中,AWQ 量化的模型和原始模型的输出差异很小。

如果单卡实在放不下,可以用多卡张量并行:

--tensor-parallel-size 2

这会把模型切分到两张卡上,每张卡承担一半的计算。但要注意,张量并行会带来卡间通信开销,所以两张卡的性能提升不是线性的,通常能到 1.6 到 1.8 倍左右。

5. 选型决策:什么场景该用哪个

5.1 一张决策流程图帮你快速判断

与其纠结,不如按下面的逻辑走一遍:

  1. 你是个人学习/做 Demo 吗?是 → Ollama。不是 → 继续。
  2. 你的日活用户超过 50 人吗?没有 → Ollama 可能够用。有 → 继续。
  3. 你需要同时处理 10 个以上的请求吗?不需要 → Ollama。需要 → vLLM。
  4. 你有运维能力来维护一个 Python 服务吗?没有 → 考虑 Ollama + 负载均衡。有 → vLLM。
  5. 你的显卡显存小于 8GB 吗?是 → Ollama(量化支持更好)。不是 → vLLM。

这个流程不是绝对的,但能帮你快速缩小选择范围。

5.2 混合方案:Ollama 做开发,vLLM 做生产

实际项目中,我经常推荐一种混合方案:开发阶段用 Ollama,因为它的模型管理太方便了,ollama pull一下就能换模型,调试 prompt 也很直观。等到要上线了,把模型权重导出,用 vLLM 部署生产服务。

这样做的好处是开发效率高,生产性能好。坏处是两边的模型格式可能不一样,Ollama 用的是 GGUF,vLLM 用的是 HuggingFace 格式。不过现在很多模型两边都有现成的版本,直接下载对应的就行,不用自己转换。

5.3 成本对比:同样并发下谁更省钱

假设你需要支持 100 个并发用户,平均每个请求生成 200 个 token。

用 Ollama 的话,根据实测,一台 4090(24GB)大概能支持 5 到 8 个并发,那你就需要大约 15 台机器。每台机器按 2 万算,硬件成本 30 万。

用 vLLM 的话,同样一台 4090,通过 PagedAttention 和连续批处理,大概能支持 50 到 80 个并发。那只需要 2 台机器,硬件成本 4 万。

这个差距是数量级的。当然,实际数字会因模型大小、序列长度、SLA 要求等因素而变化,但趋势是明确的:并发越高,vLLM 的成本优势越明显。

6. 常见问题与排查技巧实录

6.1 Ollama 常见问题速查

问题原因解决方法
下载模型太慢网络问题设置代理或找国内镜像
显存溢出模型太大或并发太高换更小的量化版本,降低 NUM_PARALLEL
服务启动失败端口被占用改端口或杀掉占用进程
输出质量差量化级别太低换 Q4_K_M 或更高
开机自启占显存默认配置禁用自启服务
API 调用超时并发过高降低并发或换 vLLM

6.2 vLLM 常见问题速查

问题原因解决方法
安装后 torch 版本变了依赖冲突用虚拟环境隔离
启动报 CUDA 错误CUDA 版本不匹配检查 CUDA 和 PyTorch 版本
显存不够模型太大或 max-model-len 太大用量化或降低序列长度
吞吐量不如预期参数没调好调整 gpu-memory-utilization 和批处理参数
Windows 下跑不起来原生支持有限用 WSL2 或 Docker
多卡通信慢张量并行开销检查 NVLink 或 PCIe 带宽

6.3 几个我踩过的坑

第一个坑是Ollama 的模型存储路径。我一开始没改,结果系统盘被撑爆了,后来迁移模型的时候又遇到权限问题,折腾了半天。建议装完第一件事就是改OLLAMA_MODELS。

第二个坑是vLLM 的版本兼容性。有一次我升级了 vLLM,结果它把我环境里的 PyTorch 从 2.1 升到了 2.3,导致另一个项目跑不起来了。从那以后我所有 vLLM 相关的环境都用独立的虚拟环境。

第三个坑是压测时的并发数设置。我一开始用 100 并发去压一个 7B 模型,结果全部超时,以为是 vLLM 不行。后来逐步降低到 30 并发,发现延迟很稳定。所以压测要从低到高逐步加,找到拐点,而不是一上来就拉满。

6.4 性能调优的几个实用技巧

对于 vLLM,有几个参数对性能影响很大:

  • --max-num-seqs:控制同时处理的最大请求数,默认 256。如果你的显存有限,可以调低。
  • --max-num-batched-tokens:控制每批处理的最大 token 数,调大可以提高吞吐但增加延迟。
  • --enable-chunked-prefill:对于长 prompt 场景,开启这个可以避免一个长请求阻塞其他请求。

对于 Ollama,除了OLLAMA_NUM_PARALLEL,还可以设置OLLAMA_MAX_LOADED_MODELS控制同时加载的模型数量。如果你只需要一个模型,设为 1 可以节省显存。

7. 从 Ollama 迁移到 vLLM 的实操路径

7.1 模型格式转换与准备

Ollama 用的 GGUF 格式不能直接给 vLLM 用。你需要找到对应的 HuggingFace 格式模型。大多数主流模型在 HuggingFace 上都有官方或社区上传的版本,直接下载就行。

如果找不到对应的 HF 格式,可以用 llama.cpp 提供的转换脚本把 GGUF 转回 HF 格式,但这个过程比较麻烦,而且可能有精度损失。我的建议是直接用 HF 上的原始模型,然后用 vLLM 支持的量化方案重新量化。

7.2 接口适配:从 Ollama API 到 OpenAI API

Ollama 的 API 格式和 OpenAI 不完全一样,迁移的时候需要改调用代码。主要差异在请求体和响应体的字段名上。比如 Ollama 用prompt,OpenAI 用messages;Ollama 返回response,OpenAI 返回choices[0].message.content。

如果你不想改代码,可以在 vLLM 前面加一个适配层,把 OpenAI 格式转换成 Ollama 格式。但更推荐直接改成 OpenAI 格式,因为这是行业标准,以后换其他推理引擎也不用再改。

7.3 灰度切换与回滚方案

迁移不要一刀切。先把 vLLM 服务部署好,用少量流量测试,确认稳定后再逐步加大流量。同时保留 Ollama 服务作为备份,万一 vLLM 出问题可以快速切回去。

具体做法可以是在网关层做流量分配,比如 10% 走 vLLM,90% 走 Ollama,观察一段时间没问题后调整为 50/50,最后全部切到 vLLM。

这个过程中要重点监控几个指标:请求延迟、错误率、GPU 利用率、显存占用。任何一个指标异常都要及时排查。

8. 一些关于推理引擎选型的个人体会

做了这么多项目,我最大的体会是:没有最好的工具,只有最合适的工具。Ollama 和 vLLM 不是竞争关系,它们服务于不同的场景。你完全可以在自己的工具箱里同时保留这两个,根据具体需求来选用。

另一个体会是,不要过早优化。如果你现在只有几个用户,用 Ollama 完全没问题,没必要为了“以后可能的高并发”提前上 vLLM,那样只会增加开发和运维的复杂度。等到真正遇到瓶颈了再迁移,成本反而更低。

最后分享一个小技巧:不管你用哪个引擎,都建议在服务前面加一层网关(比如 Nginx),这样可以方便地做负载均衡、限流、监控和灰度发布。网关层的配置和推理引擎无关,换引擎的时候这一层不用动,能省不少事。

如果你正在纠结选哪个,我的建议是:先用 Ollama 把东西跑起来,验证你的业务逻辑和 prompt 效果。等到性能真的成为瓶颈了,再考虑迁移到 vLLM。不要为了技术而技术,能解决问题的方案就是好方案。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进