<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:media="http://search.yahoo.com/mrss/">
  <channel>
    <title>VLLM · 仁才德</title>
    <link>https://jared.lynskey.co.nz/zh-cn/tags/vllm/</link>
    <description>关于软件工程与研发团队领导力的短文——绩效评估、招聘与团队扩张、CI/CD、DevOps 监控、智能体 AI。写于首尔。</description>
    <generator>Hugo</generator>
    <language>zh-CN</language>
    <copyright>© 2026 仁才德</copyright>
    <lastBuildDate>Fri, 30 Oct 2026 18:20:44 +0900</lastBuildDate>
    <atom:link href="https://jared.lynskey.co.nz/zh-cn/tags/vllm/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>vLLM 与 Ollama 对比：大模型私有化部署该选哪个</title>
      <link>https://jared.lynskey.co.nz/zh-cn/posts/2026/2026-10-30-vllm-vs-ollama/</link>
      <guid isPermaLink="true">https://jared.lynskey.co.nz/zh-cn/posts/2026/2026-10-30-vllm-vs-ollama/</guid>
      <pubDate>Fri, 30 Oct 2026 00:00:00 +1300</pubDate>
      <dc:creator>Jared Lynskey</dc:creator>
      <category>vLLM</category>
      <category>Ollama</category>
      <category>LLM</category>
      <category>Self-Hosted</category>
      <category>MLOps</category>
      <category>DevOps</category>
      <category>Docker</category>
      <category>Kubernetes</category>
      <description>vLLM 与 Ollama 对比，两个我都在用：笔记本或个人工具用 Ollama，GPU 服务器扛大量并发用 vLLM。大模型私有化部署怎么选，附安装、Docker 与 Kubernetes 的 vLLM 部署教程和踩过的坑。</description>
      <content:encoded><![CDATA[<p>如果是一个人用，或者一台机器上的一个应用要在几分钟内把模型跑起来，用 Ollama。如果是一台 GPU 服务器要同时应付大量请求，吞吐量要紧，用 vLLM。Ollama 是个模型运行器：它拉取量化好的模型，你调用时加载，你不用了就放掉。vLLM 是个推理服务器：启动时就占下 GPU 的大部分显存，再把所有正在处理的请求批在一起，让 GPU 一直有活干。两者都兼容 OpenAI API，所以调用它们的代码可以一模一样。</p>
<p>这两个我每天都在用。Ollama 跑在我的笔记本和办公室那台 GPU 工作站上，用来开发，也用来跑我自己的工具。vLLM 在一块 96 GB 的 GPU 上为我做自由职业时接的一个项目提供聊天模型，最早用 Docker Compose 跑，现在跑在一个小型 Kubernetes 集群里。这篇文章讲它们各自擅长什么、怎么安装，以及 vLLM 那边让我白白耗掉时间的几件事。</p>

<h2 class="relative group">vLLM 与 Ollama 的区别一览
    <div id="vllm-与-ollama-的区别一览" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#vllm-%e4%b8%8e-ollama-%e7%9a%84%e5%8c%ba%e5%88%ab%e4%b8%80%e8%a7%88" aria-label="锚点">#</a>
    </span>
    
</h2>
<table>
	<thead>
			<tr>
					<th></th>
					<th>Ollama</th>
					<th>vLLM</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>适合</td>
					<td>单个用户：笔记本、开发机、个人工具</td>
					<td>多用户或繁忙的应用，跑在 GPU 服务器上</td>
			</tr>
			<tr>
					<td>运行平台</td>
					<td>macOS、Linux、Windows；Apple 芯片、NVIDIA、AMD 或 CPU</td>
					<td>Linux 加 NVIDIA GPU 最省心；其他硬件也能跑，但要多折腾</td>
			</tr>
			<tr>
					<td>模型</td>
					<td>它自己模型库里的量化 GGUF（<code>qwen3:8b</code>）</td>
					<td>BF16、FP8、AWQ 或 GPTQ 格式的 Hugging Face 权重（<code>Qwen/Qwen3-8B</code>）</td>
			</tr>
			<tr>
					<td>安装</td>
					<td>一个安装包，然后 <code>ollama run</code></td>
					<td><code>pip install vllm</code>，或者 <code>vllm/vllm-openai</code> Docker 镜像</td>
			</tr>
			<tr>
					<td>显存</td>
					<td>第一次用时加载模型，空闲 5 分钟后卸载</td>
					<td>启动时预留显卡的固定比例（默认 90% 以上），一直占着</td>
			</tr>
			<tr>
					<td>同时处理多个请求</td>
					<td>几个并行槽位</td>
					<td>连续批处理（continuous batching），它就是为这个而生的</td>
			</tr>
			<tr>
					<td>启动</td>
					<td>小模型几秒钟</td>
					<td>大模型要几分钟</td>
			</tr>
			<tr>
					<td>LoRA adapter</td>
					<td>用 Modelfile 打包成一个新模型</td>
					<td>一个服务挂多个，每个请求自己选</td>
			</tr>
			<tr>
					<td>API</td>
					<td>自己的 API，外加 <code>:11434/v1</code> 上的 OpenAI 兼容接口</td>
					<td><code>:8000/v1</code> 上的 OpenAI 兼容接口</td>
			</tr>
	</tbody>
</table>

<h2 class="relative group">Ollama 是什么
    <div id="ollama-是什么" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#ollama-%e6%98%af%e4%bb%80%e4%b9%88" aria-label="锚点">#</a>
    </span>
    
</h2>
<p>Ollama 是我知道的、在自己机器上让模型开口回答最省事的办法。它脱胎于 llama.cpp，跑的是量化过的 GGUF 模型，所以一个 8B 模型大约 5 GB 就装得下，在 MacBook 上也跑得挺欢。按名字拉取模型，第一次调用时加载，闲置五分钟没事干就从内存里退出，所以它能和你机器上跑着的其他东西客客气气地共处。</p>
<p>这份客气就是代价。Ollama 是围绕&quot;一次一个人&quot;来设计的。它能并行回答几个请求，但每个并行槽位都要占内存，而且它从来就不是为了把几十个用户批到一块 GPU 上而做的。</p>

<h2 class="relative group">vLLM 是什么
    <div id="vllm-是什么" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#vllm-%e6%98%af%e4%bb%80%e4%b9%88" aria-label="锚点">#</a>
    </span>
    
</h2>
<p>vLLM 是一个推理服务引擎，正是为这种批处理而做的。撑起它的主要是两个设计。PagedAttention 把每个请求的 KV cache（模型在读写 token 的过程中积累下来的 attention 状态）存放在固定大小的小块里，显存就不会浪费在 padding 和碎片上。连续批处理让新请求在两个生成步骤之间加入批次，而不必等当前这一批跑完。两者加在一起，同一块 GPU 跑 vLLM 能服务的并发用户，远多于它一个接一个处理请求的时候。</p>
<p>代价是 vLLM 表现得好像整块卡都归它。它在启动时就申领自己那份显存，权重用不完的部分全部填上 KV cache，不管有没有人在调用都一直占着。它还要完整的 Hugging Face 权重，或者 FP8 这类适合 GPU 的量化格式，而不是 Ollama 用的那种 GGUF 文件。</p>

<h2 class="relative group">一套 API，两个服务端
    <div id="一套-api两个服务端" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e4%b8%80%e5%a5%97-api%e4%b8%a4%e4%b8%aa%e6%9c%8d%e5%8a%a1%e7%ab%af" aria-label="锚点">#</a>
    </span>
    
</h2>
<p>因为两者都提供 OpenAI 的 chat completions API，我的代码并不知道自己在跟哪一个说话。URL 和模型名都从环境变量里读：</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">os</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">openai</span> <span class="kn">import</span> <span class="n">OpenAI</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Laptop:     LLM_URL=http://localhost:11434/v1  LLM_MODEL=qwen3:8b</span>
</span></span><span class="line"><span class="cl"><span class="c1"># GPU server: LLM_URL=http://localhost:8000/v1   LLM_MODEL=qwen3-8b</span>
</span></span><span class="line"><span class="cl"><span class="n">client</span> <span class="o">=</span> <span class="n">OpenAI</span><span class="p">(</span><span class="n">base_url</span><span class="o">=</span><span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s2">&#34;LLM_URL&#34;</span><span class="p">],</span> <span class="n">api_key</span><span class="o">=</span><span class="s2">&#34;not-needed&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">reply</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">chat</span><span class="o">.</span><span class="n">completions</span><span class="o">.</span><span class="n">create</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">model</span><span class="o">=</span><span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s2">&#34;LLM_MODEL&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="n">messages</span><span class="o">=</span><span class="p">[{</span><span class="s2">&#34;role&#34;</span><span class="p">:</span> <span class="s2">&#34;user&#34;</span><span class="p">,</span> <span class="s2">&#34;content&#34;</span><span class="p">:</span> <span class="s2">&#34;Say hello in Korean.&#34;</span><span class="p">}],</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">reply</span><span class="o">.</span><span class="n">choices</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="n">message</span><span class="o">.</span><span class="n">content</span><span class="p">)</span></span></span></code></pre></div></div>
<p>我的笔记本没有大显卡，在上面应用就指向 Ollama。到了服务器上，它指向 vLLM。别的什么都不用改，开发这个应用时，那台 GPU 机器甚至可以不开机。vLLM 这边，<code>--served-model-name</code> 给模型起一个短名字，客户端就不用发完整的 Hugging Face 路径。</p>
<p>有一点要提醒一下，因为很容易忘：同一个模型，两边给出的回答并不一样。Ollama 的 <code>qwen3:8b</code> 是 4-bit 量化，而在 vLLM 上你通常跑的是 16-bit 或 FP8 权重。对着其中一个调好的 prompt，换到另一个上可能表现不同，所以凡是你在衡量的东西，不管是回答质量、拒答，还是 JSON 能不能解析，都得在生产环境真正要用的那个服务上验证。</p>

<h2 class="relative group">如何安装 Ollama
    <div id="如何安装-ollama" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e5%a6%82%e4%bd%95%e5%ae%89%e8%a3%85-ollama" aria-label="锚点">#</a>
    </span>
    
</h2>
<p>在 Mac 或 Windows 上，从 ollama.com 下载应用。在 Linux 上：</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -fsSL https://ollama.com/install.sh <span class="p">|</span> sh
</span></span><span class="line"><span class="cl">ollama run qwen3:8b</span></span></code></pre></div></div>
<p>就这么多。API 现在在 <code>http://localhost:11434</code> 上，除非你设置了 <code>OLLAMA_HOST</code>，否则它只监听 localhost，这正是你想要的。我的那个跑在办公室工作站上，绑定 <code>127.0.0.1</code>，我在任何地方都通过一条隧道连过去（<a href="https://jared.lynskey.co.nz/zh-cn/posts/2026/2026-10-01-cloudflare-warp-dev-network/" >我是怎么用 Cloudflare WARP 搭起来的</a>）。</p>

<h2 class="relative group">vLLM 部署教程：安装与运行
    <div id="vllm-部署教程安装与运行" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#vllm-%e9%83%a8%e7%bd%b2%e6%95%99%e7%a8%8b%e5%ae%89%e8%a3%85%e4%b8%8e%e8%bf%90%e8%a1%8c" aria-label="锚点">#</a>
    </span>
    
</h2>
<p>在装有 NVIDIA GPU、驱动也比较新的 Linux 上，用 vLLM 部署 Qwen3-8B：</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">python -m venv .venv <span class="o">&amp;&amp;</span> <span class="nb">source</span> .venv/bin/activate
</span></span><span class="line"><span class="cl">pip install vllm
</span></span><span class="line"><span class="cl">vllm serve Qwen/Qwen3-8B --served-model-name qwen3-8b --max-model-len <span class="m">16384</span></span></span></code></pre></div></div>
<p>第一次运行会从 Hugging Face 下载权重。等日志里出现 <code>Application startup complete</code>，<code>curl localhost:8000/v1/models</code> 就能列出这个模型，上面那段 Python 也能直接对着它用。</p>

<h2 class="relative group">用 Docker 部署 vLLM
    <div id="用-docker-部署-vllm" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e7%94%a8-docker-%e9%83%a8%e7%bd%b2-vllm" aria-label="锚点">#</a>
    </span>
    
</h2>
<p>凡是需要一直在线的，我都用官方镜像而不是 pip 安装，主要是为了让 CUDA、PyTorch 和 vLLM 的版本绑在一起。一个通用的 Compose 服务：</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">vllm</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">vllm/vllm-openai:v0.30.0  </span><span class="w"> </span><span class="c"># pin a tag; the image is about 30 GB</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ipc</span><span class="p">:</span><span class="w"> </span><span class="l">host                         </span><span class="w"> </span><span class="c"># PyTorch needs shared memory</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">command</span><span class="p">:</span><span class="w"> </span><span class="p">&gt;</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">      --model Qwen/Qwen3-8B
</span></span></span><span class="line"><span class="cl"><span class="sd">      --served-model-name qwen3-8b
</span></span></span><span class="line"><span class="cl"><span class="sd">      --gpu-memory-utilization 0.85
</span></span></span><span class="line"><span class="cl"><span class="sd">      --max-model-len 16384</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">${HOME}/.cache/huggingface:/root/.cache/huggingface</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;127.0.0.1:8000:8000&#34;</span><span class="w">          </span><span class="c"># reach it over ssh -L, not the LAN</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">deploy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">resources</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">reservations</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">devices</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="nt">driver</span><span class="p">:</span><span class="w"> </span><span class="l">nvidia</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">count</span><span class="p">:</span><span class="w"> </span><span class="l">all</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">capabilities</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">gpu]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">healthcheck</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">test</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;CMD&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;python3&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;-c&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;import urllib.request; urllib.request.urlopen(&#39;http://localhost:8000/health&#39;)&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">interval</span><span class="p">:</span><span class="w"> </span><span class="l">10s</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">timeout</span><span class="p">:</span><span class="w"> </span><span class="l">5s</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">retries</span><span class="p">:</span><span class="w"> </span><span class="m">90</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">start_period</span><span class="p">:</span><span class="w"> </span><span class="l">120s</span></span></span></code></pre></div></div>
<p>tag 一定要固定。vLLM 迭代很快，flag 和默认值在版本之间会变，什么时候跟着变，应该由你自己说了算。<code>ipc: host</code>（或者给足 <code>shm_size</code>）写在 vLLM 自己的 Docker 文档里。那个很长的 <code>start_period</code>，就是下面要讲的第一件事。</p>

<h2 class="relative group">跑 vLLM 踩过的坑
    <div id="跑-vllm-踩过的坑" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e8%b7%91-vllm-%e8%b8%a9%e8%bf%87%e7%9a%84%e5%9d%91" aria-label="锚点">#</a>
    </span>
    
</h2>
<p><strong>启动要好几分钟。</strong> 加载权重、编译、捕获 CUDA graph，在我的服务器上花了 190 秒，大模型要三到五分钟很正常。照着 Web 应用写的健康检查，会在它准备好之前就把它杀掉。在 Compose 里，这意味着要设一个很长的 <code>start_period</code>；在 Kubernetes 里，意味着要配 startup probe。这也意味着每次重启都有几分钟没有模型可用，所以重启不能随手就来。</p>
<p><strong>你给它多少显存，它启动时就拿走多少。</strong> <code>--gpu-memory-utilization</code> 是 vLLM 可以使用的显卡占比，较新的版本默认 0.92，权重用不完的部分都会变成 KV cache。vLLM 独占显卡时这没什么问题。我的不是，旁边还跑着几个小模型，所以我在这块 96 GB 的卡上设成了 0.70。模型是一个 FP8 的 35B 混合专家模型（MoE），加载后占 47.7 GiB，给 KV cache 留了 15 GiB，在 16k 上下文下绰绰有余。如果 vLLM 因为最大序列长度放不进 KV cache 而拒绝启动，先调低 <code>--max-model-len</code>，再去考虑换更大的 GPU。</p>
<p><strong>LoRA 槽位要提前占显存。</strong> vLLM 可以在一个基座模型上挂多个 LoRA adapter，每个请求按模型名切换，这一点 Ollama 基本做不到。但它在启动时就会为 <code>--max-loras</code> 个 adapter 按 <code>--max-lora-rank</code> 预留空间，而在混合专家模型上，这份预留连专家部分也要算进去，所以涨得很快。在 0.70 下，四个 rank 64 的 adapter 放不下。两个可以。</p>
<p><strong>默认的序列数上限不适合混合架构的模型。</strong> 我跑的这个模型在 attention 层之间夹着 Mamba 风格的层，每个序列都需要为这些层单独准备一个缓存块。在那块卡上用 vLLM 默认的 <code>--max-num-seqs</code>，它启动失败；改成 <code>--max-num-seqs 256</code> 就好了。如果某个较新的架构起不来，先查这个 flag，再去想更偏门的原因。</p>
<p><strong>一个叫 <code>vllm</code> 的 Kubernetes Service 会让 vLLM 崩溃。</strong> 这是我最喜欢的一个。Kubernetes 会为命名空间里的每个 Service 注入环境变量，变量名取自 Service 的名字，所以一个叫 <code>vllm</code> 的 Service 会给每个 pod 塞进一个 <code>VLLM_PORT=tcp://&lt;cluster-ip&gt;:8000</code>。vLLM 会把 <code>VLLM_PORT</code> 当成自己的配置项来读，它解析不了那里的 URL，于是挂了。当然是在把整个模型加载完之后才挂的，所以每试一次就搭进去三分钟。vLLM 的文档其实对此有警告，这是我事后才看到的。解决办法是在 pod 上设 <code>enableServiceLinks: false</code>，或者给 Service 换个名字。</p>
<p>下面是一个能正常工作的 Deployment 的大致样子，只保留了要紧的部分：</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">apps/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Deployment</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">vllm</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">replicas</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">strategy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">Recreate              </span><span class="w"> </span><span class="c"># the old pod has to free the GPU first</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">selector</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">matchLabels</span><span class="p">:</span><span class="w"> </span>{<span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">vllm}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">template</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">labels</span><span class="p">:</span><span class="w"> </span>{<span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">vllm}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">enableServiceLinks</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">  </span><span class="c"># no VLLM_PORT=tcp://... from the Service</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">containers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">vllm</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">vllm/vllm-openai:v0.30.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">args</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;--model=Qwen/Qwen3-8B&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;--served-model-name=qwen3-8b&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">resources</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">limits</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">nvidia.com/gpu</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;1&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">memory</span><span class="p">:</span><span class="w"> </span><span class="l">32Gi      </span><span class="w"> </span><span class="c"># loading needs far more host RAM than serving</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">volumeMounts</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- {<span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="nt">shm, mountPath</span><span class="p">:</span><span class="w"> </span><span class="l">/dev/shm}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">startupProbe</span><span class="p">:</span><span class="w">          </span><span class="c"># don&#39;t judge it until the model is loaded</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">httpGet</span><span class="p">:</span><span class="w"> </span>{<span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="nt">/health, port</span><span class="p">:</span><span class="w"> </span><span class="m">8000</span>}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">initialDelaySeconds</span><span class="p">:</span><span class="w"> </span><span class="m">120</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">periodSeconds</span><span class="p">:</span><span class="w"> </span><span class="m">10</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">failureThreshold</span><span class="p">:</span><span class="w"> </span><span class="m">90</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">livenessProbe</span><span class="p">:</span><span class="w">         </span><span class="c"># a restart costs a full reload, so be patient</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">httpGet</span><span class="p">:</span><span class="w"> </span>{<span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="nt">/health, port</span><span class="p">:</span><span class="w"> </span><span class="m">8000</span>}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">periodSeconds</span><span class="p">:</span><span class="w"> </span><span class="m">30</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">timeoutSeconds</span><span class="p">:</span><span class="w"> </span><span class="m">5</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">failureThreshold</span><span class="p">:</span><span class="w"> </span><span class="m">10</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">shm</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">emptyDir</span><span class="p">:</span><span class="w"> </span>{<span class="nt">medium</span><span class="p">:</span><span class="w"> </span><span class="nt">Memory, sizeLimit</span><span class="p">:</span><span class="w"> </span><span class="l">16Gi}</span></span></span></code></pre></div></div>
<p>值得照抄的是 probe 这部分。startup probe 先等两分钟，然后再给模型十五分钟时间起来，过了这个时间 Kubernetes 才判定它挂了。liveness probe 要连续失败大约五分钟才会重启它，因为引擎忙的时候 <code>/health</code> 可能响应很慢，而且重启反正要完整重新加载一遍。在单 GPU 上 <code>Recreate</code> 很关键：滚动更新会先启动新 pod，而它只能干等着那块还被旧 pod 占着的 GPU。内存限制看起来给得很宽，确实很宽；权重进了 GPU 之后，我这边稳定在大约 7 GiB 主机内存，但加载过程本身要多得多。</p>

<h2 class="relative group">Ollama 要注意的地方
    <div id="ollama-要注意的地方" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#ollama-%e8%a6%81%e6%b3%a8%e6%84%8f%e7%9a%84%e5%9c%b0%e6%96%b9" aria-label="锚点">#</a>
    </span>
    
</h2>
<p>意外少一些，主要是因为它做的事本来就少。</p>
<p>安静一阵之后的第一个请求会很慢，因为模型已经被卸载，得重新加载。<code>OLLAMA_KEEP_ALIVE</code> 决定模型在最后一个请求之后在内存里留多久；如果这个停顿让你烦，就把它调大，或者在一台不干别的事的机器上设成 <code>-1</code>。</p>
<p>上下文窗口取决于你有多少显存。显存不到 24 GiB 时，Ollama 默认只给 4k token，远少于大多数模型支持的长度，而放不下的 prompt 会被悄悄截掉，不会报错拒绝。如果回答好像无视了长 prompt 的开头，就把 <code>OLLAMA_CONTEXT_LENGTH</code>（或者每个请求的 <code>num_ctx</code>）设成你真正需要的长度。</p>
<p>如果你把远程的 Ollama 转发到笔记本上，别用 11434。那是 Ollama 的默认端口，要是本地哪天也起了一个 Ollama，两者就会抢这个端口，你可能在不知不觉中连到了错的那一个。我的转发到 11435。</p>
<p>还有，它不是多用户服务器。拿来做几个人用的内部工具没问题；一旦真正的并发来了，就到了该换 vLLM 的时候。</p>

<h2 class="relative group">需要多少显存
    <div id="需要多少显存" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e9%9c%80%e8%a6%81%e5%a4%9a%e5%b0%91%e6%98%be%e5%ad%98" aria-label="锚点">#</a>
    </span>
    
</h2>
<p>只算权重的大致大小：</p>
<table>
	<thead>
			<tr>
					<th>模型大小</th>
					<th>16-bit（BF16）</th>
					<th>8-bit（FP8）</th>
					<th>4-bit GGUF（Q4_K_M）</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>8B</td>
					<td>~16 GB</td>
					<td>~8 GB</td>
					<td>~5 GB</td>
			</tr>
			<tr>
					<td>32B</td>
					<td>~64 GB</td>
					<td>~32 GB</td>
					<td>~20 GB</td>
			</tr>
			<tr>
					<td>70B</td>
					<td>~140 GB</td>
					<td>~70 GB</td>
					<td>~43 GB</td>
			</tr>
	</tbody>
</table>
<p>也就是 16-bit 下每个参数约 2 字节，8-bit 下 1 字节，Q4_K_M 下略多于半个字节。权重之外还要加上 KV cache，它随上下文长度和同时在处理的请求数增长。正是这第二部分，让同一个模型在 vLLM 上服务五十个用户时，比在 Ollama 上只服务你一个人时需要多得多的显存。</p>

<h2 class="relative group">该用哪一个
    <div id="该用哪一个" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e8%af%a5%e7%94%a8%e5%93%aa%e4%b8%80%e4%b8%aa" aria-label="锚点">#</a>
    </span>
    
</h2>
<p>如果你只是想在自己的机器上跑个模型，装 Ollama 就够了，到此为止；十分钟内就能让它回答问题。如果你有一台 GPU 服务器，还有用户或者应用整天都要调用它，那就用 vLLM，接受它启动慢、占显存，把 probe 配好。不少部署最后两个都用，我的也是：写代码的地方用 Ollama，请求落地的地方用 vLLM。</p>
<p>当然还有别的选择。SGLang 是 vLLM 常见的替代方案，但我没在生产环境跑过它，与其瞎猜，不如不写。如果模型得跑在手机上，那就又回到 llama.cpp 了，我在<a href="https://jared.lynskey.co.nz/zh-cn/posts/2026/2026-01-30-on-device-ai/" >用 React Native 在手机上端侧运行 LLM</a> 里写过。</p>
]]></content:encoded>
      <media:content url="https://jared.lynskey.co.nz/en/posts/2026/2026-10-30-vllm-vs-ollama/featured.jpg" medium="image" type="image/jpeg" />
    </item>
  </channel>
</rss>
