↓ 跳过正文

VLLM Ollama LLM

vLLM 与 Ollama 对比:大模型私有化部署该选哪个

vLLM 与 Ollama 对比,两个我都在用:笔记本或个人工具用 Ollama,GPU 服务器扛大量并发用 vLLM。大模型私有化部署怎么选,附安装、Docker 与 Kubernetes 的 vLLM 部署教程和踩过的坑。

vLLM 与 Ollama 对比:大模型私有化部署该选哪个

如果是一个人用,或者一台机器上的一个应用要在几分钟内把模型跑起来,用 Ollama。如果是一台 GPU 服务器要同时应付大量请求,吞吐量要紧,用 vLLM。Ollama 是个模型运行器:它拉取量化好的模型,你调用时加载,你不用了就放掉。vLLM 是个推理服务器:启动时就占下 GPU 的大部分显存,再把所有正在处理的请求批在一起,让 GPU 一直有活干。两者都兼容 OpenAI API,所以调用它们的代码可以一模一样。

这两个我每天都在用。Ollama 跑在我的笔记本和办公室那台 GPU 工作站上,用来开发,也用来跑我自己的工具。vLLM 在一块 96 GB 的 GPU 上为我做自由职业时接的一个项目提供聊天模型,最早用 Docker Compose 跑,现在跑在一个小型 Kubernetes 集群里。这篇文章讲它们各自擅长什么、怎么安装,以及 vLLM 那边让我白白耗掉时间的几件事。

vLLM 与 Ollama 的区别一览
#

OllamavLLM
适合单个用户:笔记本、开发机、个人工具多用户或繁忙的应用,跑在 GPU 服务器上
运行平台macOS、Linux、Windows;Apple 芯片、NVIDIA、AMD 或 CPULinux 加 NVIDIA GPU 最省心;其他硬件也能跑,但要多折腾
模型它自己模型库里的量化 GGUF(qwen3:8b)BF16、FP8、AWQ 或 GPTQ 格式的 Hugging Face 权重(Qwen/Qwen3-8B)
安装一个安装包,然后 ollama runpip install vllm,或者 vllm/vllm-openai Docker 镜像
显存第一次用时加载模型,空闲 5 分钟后卸载启动时预留显卡的固定比例(默认 90% 以上),一直占着
同时处理多个请求几个并行槽位连续批处理(continuous batching),它就是为这个而生的
启动小模型几秒钟大模型要几分钟
LoRA adapter用 Modelfile 打包成一个新模型一个服务挂多个,每个请求自己选
API自己的 API,外加 :11434/v1 上的 OpenAI 兼容接口:8000/v1 上的 OpenAI 兼容接口

Ollama 是什么
#

Ollama 是我知道的、在自己机器上让模型开口回答最省事的办法。它脱胎于 llama.cpp,跑的是量化过的 GGUF 模型,所以一个 8B 模型大约 5 GB 就装得下,在 MacBook 上也跑得挺欢。按名字拉取模型,第一次调用时加载,闲置五分钟没事干就从内存里退出,所以它能和你机器上跑着的其他东西客客气气地共处。

这份客气就是代价。Ollama 是围绕"一次一个人"来设计的。它能并行回答几个请求,但每个并行槽位都要占内存,而且它从来就不是为了把几十个用户批到一块 GPU 上而做的。

vLLM 是什么
#

vLLM 是一个推理服务引擎,正是为这种批处理而做的。撑起它的主要是两个设计。PagedAttention 把每个请求的 KV cache(模型在读写 token 的过程中积累下来的 attention 状态)存放在固定大小的小块里,显存就不会浪费在 padding 和碎片上。连续批处理让新请求在两个生成步骤之间加入批次,而不必等当前这一批跑完。两者加在一起,同一块 GPU 跑 vLLM 能服务的并发用户,远多于它一个接一个处理请求的时候。

代价是 vLLM 表现得好像整块卡都归它。它在启动时就申领自己那份显存,权重用不完的部分全部填上 KV cache,不管有没有人在调用都一直占着。它还要完整的 Hugging Face 权重,或者 FP8 这类适合 GPU 的量化格式,而不是 Ollama 用的那种 GGUF 文件。

一套 API,两个服务端
#

因为两者都提供 OpenAI 的 chat completions API,我的代码并不知道自己在跟哪一个说话。URL 和模型名都从环境变量里读:

import os
from openai import OpenAI

# Laptop:     LLM_URL=http://localhost:11434/v1  LLM_MODEL=qwen3:8b
# GPU server: LLM_URL=http://localhost:8000/v1   LLM_MODEL=qwen3-8b
client = OpenAI(base_url=os.environ["LLM_URL"], api_key="not-needed")

reply = client.chat.completions.create(
    model=os.environ["LLM_MODEL"],
    messages=[{"role": "user", "content": "Say hello in Korean."}],
)
print(reply.choices[0].message.content)

我的笔记本没有大显卡,在上面应用就指向 Ollama。到了服务器上,它指向 vLLM。别的什么都不用改,开发这个应用时,那台 GPU 机器甚至可以不开机。vLLM 这边,--served-model-name 给模型起一个短名字,客户端就不用发完整的 Hugging Face 路径。

有一点要提醒一下,因为很容易忘:同一个模型,两边给出的回答并不一样。Ollama 的 qwen3:8b 是 4-bit 量化,而在 vLLM 上你通常跑的是 16-bit 或 FP8 权重。对着其中一个调好的 prompt,换到另一个上可能表现不同,所以凡是你在衡量的东西,不管是回答质量、拒答,还是 JSON 能不能解析,都得在生产环境真正要用的那个服务上验证。

如何安装 Ollama
#

在 Mac 或 Windows 上,从 ollama.com 下载应用。在 Linux 上:

curl -fsSL https://ollama.com/install.sh | sh
ollama run qwen3:8b

就这么多。API 现在在 http://localhost:11434 上,除非你设置了 OLLAMA_HOST,否则它只监听 localhost,这正是你想要的。我的那个跑在办公室工作站上,绑定 127.0.0.1,我在任何地方都通过一条隧道连过去(我是怎么用 Cloudflare WARP 搭起来的)。

vLLM 部署教程:安装与运行
#

在装有 NVIDIA GPU、驱动也比较新的 Linux 上,用 vLLM 部署 Qwen3-8B:

python -m venv .venv && source .venv/bin/activate
pip install vllm
vllm serve Qwen/Qwen3-8B --served-model-name qwen3-8b --max-model-len 16384

第一次运行会从 Hugging Face 下载权重。等日志里出现 Application startup complete,curl localhost:8000/v1/models 就能列出这个模型,上面那段 Python 也能直接对着它用。

用 Docker 部署 vLLM
#

凡是需要一直在线的,我都用官方镜像而不是 pip 安装,主要是为了让 CUDA、PyTorch 和 vLLM 的版本绑在一起。一个通用的 Compose 服务:

services:
  vllm:
    image: vllm/vllm-openai:v0.30.0   # pin a tag; the image is about 30 GB
    restart: unless-stopped
    ipc: host                          # PyTorch needs shared memory
    command: >
      --model Qwen/Qwen3-8B
      --served-model-name qwen3-8b
      --gpu-memory-utilization 0.85
      --max-model-len 16384
    volumes:
      - ${HOME}/.cache/huggingface:/root/.cache/huggingface
    ports:
      - "127.0.0.1:8000:8000"          # reach it over ssh -L, not the LAN
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: all
              capabilities: [gpu]
    healthcheck:
      test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"]
      interval: 10s
      timeout: 5s
      retries: 90
      start_period: 120s

tag 一定要固定。vLLM 迭代很快,flag 和默认值在版本之间会变,什么时候跟着变,应该由你自己说了算。ipc: host(或者给足 shm_size)写在 vLLM 自己的 Docker 文档里。那个很长的 start_period,就是下面要讲的第一件事。

跑 vLLM 踩过的坑
#

启动要好几分钟。 加载权重、编译、捕获 CUDA graph,在我的服务器上花了 190 秒,大模型要三到五分钟很正常。照着 Web 应用写的健康检查,会在它准备好之前就把它杀掉。在 Compose 里,这意味着要设一个很长的 start_period;在 Kubernetes 里,意味着要配 startup probe。这也意味着每次重启都有几分钟没有模型可用,所以重启不能随手就来。

你给它多少显存,它启动时就拿走多少。 --gpu-memory-utilization 是 vLLM 可以使用的显卡占比,较新的版本默认 0.92,权重用不完的部分都会变成 KV cache。vLLM 独占显卡时这没什么问题。我的不是,旁边还跑着几个小模型,所以我在这块 96 GB 的卡上设成了 0.70。模型是一个 FP8 的 35B 混合专家模型(MoE),加载后占 47.7 GiB,给 KV cache 留了 15 GiB,在 16k 上下文下绰绰有余。如果 vLLM 因为最大序列长度放不进 KV cache 而拒绝启动,先调低 --max-model-len,再去考虑换更大的 GPU。

LoRA 槽位要提前占显存。 vLLM 可以在一个基座模型上挂多个 LoRA adapter,每个请求按模型名切换,这一点 Ollama 基本做不到。但它在启动时就会为 --max-loras 个 adapter 按 --max-lora-rank 预留空间,而在混合专家模型上,这份预留连专家部分也要算进去,所以涨得很快。在 0.70 下,四个 rank 64 的 adapter 放不下。两个可以。

默认的序列数上限不适合混合架构的模型。 我跑的这个模型在 attention 层之间夹着 Mamba 风格的层,每个序列都需要为这些层单独准备一个缓存块。在那块卡上用 vLLM 默认的 --max-num-seqs,它启动失败;改成 --max-num-seqs 256 就好了。如果某个较新的架构起不来,先查这个 flag,再去想更偏门的原因。

一个叫 vllm 的 Kubernetes Service 会让 vLLM 崩溃。 这是我最喜欢的一个。Kubernetes 会为命名空间里的每个 Service 注入环境变量,变量名取自 Service 的名字,所以一个叫 vllm 的 Service 会给每个 pod 塞进一个 VLLM_PORT=tcp://<cluster-ip>:8000。vLLM 会把 VLLM_PORT 当成自己的配置项来读,它解析不了那里的 URL,于是挂了。当然是在把整个模型加载完之后才挂的,所以每试一次就搭进去三分钟。vLLM 的文档其实对此有警告,这是我事后才看到的。解决办法是在 pod 上设 enableServiceLinks: false,或者给 Service 换个名字。

下面是一个能正常工作的 Deployment 的大致样子,只保留了要紧的部分:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: vllm
spec:
  replicas: 1
  strategy:
    type: Recreate               # the old pod has to free the GPU first
  selector:
    matchLabels: {app: vllm}
  template:
    metadata:
      labels: {app: vllm}
    spec:
      enableServiceLinks: false  # no VLLM_PORT=tcp://... from the Service
      containers:
        - name: vllm
          image: vllm/vllm-openai:v0.30.0
          args: ["--model=Qwen/Qwen3-8B", "--served-model-name=qwen3-8b"]
          resources:
            limits:
              nvidia.com/gpu: "1"
              memory: 32Gi       # loading needs far more host RAM than serving
          volumeMounts:
            - {name: shm, mountPath: /dev/shm}
          startupProbe:          # don't judge it until the model is loaded
            httpGet: {path: /health, port: 8000}
            initialDelaySeconds: 120
            periodSeconds: 10
            failureThreshold: 90
          livenessProbe:         # a restart costs a full reload, so be patient
            httpGet: {path: /health, port: 8000}
            periodSeconds: 30
            timeoutSeconds: 5
            failureThreshold: 10
      volumes:
        - name: shm
          emptyDir: {medium: Memory, sizeLimit: 16Gi}

值得照抄的是 probe 这部分。startup probe 先等两分钟,然后再给模型十五分钟时间起来,过了这个时间 Kubernetes 才判定它挂了。liveness probe 要连续失败大约五分钟才会重启它,因为引擎忙的时候 /health 可能响应很慢,而且重启反正要完整重新加载一遍。在单 GPU 上 Recreate 很关键:滚动更新会先启动新 pod,而它只能干等着那块还被旧 pod 占着的 GPU。内存限制看起来给得很宽,确实很宽;权重进了 GPU 之后,我这边稳定在大约 7 GiB 主机内存,但加载过程本身要多得多。

Ollama 要注意的地方
#

意外少一些,主要是因为它做的事本来就少。

安静一阵之后的第一个请求会很慢,因为模型已经被卸载,得重新加载。OLLAMA_KEEP_ALIVE 决定模型在最后一个请求之后在内存里留多久;如果这个停顿让你烦,就把它调大,或者在一台不干别的事的机器上设成 -1。

上下文窗口取决于你有多少显存。显存不到 24 GiB 时,Ollama 默认只给 4k token,远少于大多数模型支持的长度,而放不下的 prompt 会被悄悄截掉,不会报错拒绝。如果回答好像无视了长 prompt 的开头,就把 OLLAMA_CONTEXT_LENGTH(或者每个请求的 num_ctx)设成你真正需要的长度。

如果你把远程的 Ollama 转发到笔记本上,别用 11434。那是 Ollama 的默认端口,要是本地哪天也起了一个 Ollama,两者就会抢这个端口,你可能在不知不觉中连到了错的那一个。我的转发到 11435。

还有,它不是多用户服务器。拿来做几个人用的内部工具没问题;一旦真正的并发来了,就到了该换 vLLM 的时候。

需要多少显存
#

只算权重的大致大小:

模型大小16-bit(BF16)8-bit(FP8)4-bit GGUF(Q4_K_M)
8B~16 GB~8 GB~5 GB
32B~64 GB~32 GB~20 GB
70B~140 GB~70 GB~43 GB

也就是 16-bit 下每个参数约 2 字节,8-bit 下 1 字节,Q4_K_M 下略多于半个字节。权重之外还要加上 KV cache,它随上下文长度和同时在处理的请求数增长。正是这第二部分,让同一个模型在 vLLM 上服务五十个用户时,比在 Ollama 上只服务你一个人时需要多得多的显存。

该用哪一个
#

如果你只是想在自己的机器上跑个模型,装 Ollama 就够了,到此为止;十分钟内就能让它回答问题。如果你有一台 GPU 服务器,还有用户或者应用整天都要调用它,那就用 vLLM,接受它启动慢、占显存,把 probe 配好。不少部署最后两个都用,我的也是:写代码的地方用 Ollama,请求落地的地方用 vLLM。

当然还有别的选择。SGLang 是 vLLM 常见的替代方案,但我没在生产环境跑过它,与其瞎猜,不如不写。如果模型得跑在手机上,那就又回到 llama.cpp 了,我在用 React Native 在手机上端侧运行 LLM 里写过。

评论

评论经审核后显示。