<?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/ko/tags/vllm/</link>
    <description>소프트웨어 엔지니어링과 개발 조직 리더십에 대한 이야기. 성과 리뷰, 채용과 팀 확장, CI/CD, 데브옵스 모니터링, 에이전틱 AI. 서울에서 씁니다.</description>
    <generator>Hugo</generator>
    <language>ko</language>
    <copyright>© 2026 인재덕</copyright>
    <lastBuildDate>Fri, 30 Oct 2026 18:20:44 +0900</lastBuildDate>
    <atom:link href="https://jared.lynskey.co.nz/ko/tags/vllm/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>vLLM vs Ollama 비교: 셀프 호스팅 LLM, 언제 무엇을 쓸까</title>
      <link>https://jared.lynskey.co.nz/ko/posts/2026/2026-10-30-vllm-vs-ollama/</link>
      <guid isPermaLink="true">https://jared.lynskey.co.nz/ko/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 vs Ollama 비교. 혼자 쓸 땐 Ollama, 요청이 많은 GPU 서버엔 vLLM. 설치 방법과 Docker·Kubernetes 구성, 겪은 문제까지 담았습니다.</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 vs Ollama 한눈에 비교
    <div id="vllm-vs-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-vs-ollama-%ed%95%9c%eb%88%88%ec%97%90-%eb%b9%84%ea%b5%90" 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 silicon, NVIDIA, AMD, 또는 CPU</td>
					<td>NVIDIA GPU가 달린 Linux가 가장 순탄하고, 다른 하드웨어는 품이 더 듭니다</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>GPU 메모리</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 어댑터</td>
					<td>Modelfile로 새 모델에 구워 넣습니다</td>
					<td>서버 하나에 여러 개, 요청마다 골라 씁니다</td>
			</tr>
			<tr>
					<td>API</td>
					<td>자체 API, 그리고 <code>:11434/v1</code>의 OpenAI 호환 API</td>
					<td><code>:8000/v1</code>의 OpenAI 호환 API</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%eb%9e%80" aria-label="앵커">#</a>
    </span>
    
</h2>
<p>Ollama는 제가 아는 한 내 기계에서 모델이 답하게 만드는 가장 쉬운 방법입니다. llama.cpp에서 출발했고 양자화된 GGUF 모델을 돌리는데, 그래서 8B 모델이 5 GB 정도에 들어가고 MacBook에서도 무리 없이 돌아갑니다. 모델을 이름으로 받아 두면 처음 호출할 때 로드되고, 5분 동안 할 일이 없으면 메모리에서 내려갑니다. 덕분에 같은 기계에서 돌고 있는 다른 것들과 얌전하게 자원을 나눠 씁니다.</p>
<p>그 얌전함이 곧 트레이드오프입니다. Ollama는 한 번에 한 사람이 쓰는 걸 전제로 만들어졌습니다. 몇 개의 요청에 병렬로 답할 수는 있지만 병렬 슬롯마다 메모리가 들고, 수십 명의 사용자를 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%ec%9d%b4%eb%9e%80" aria-label="앵커">#</a>
    </span>
    
</h2>
<p>vLLM은 서빙 엔진이고, 바로 그 배칭을 위해 만들어졌습니다. 핵심 아이디어는 두 가지입니다. PagedAttention은 요청마다의 KV 캐시(모델이 토큰을 읽고 쓰면서 쌓아 가는 어텐션 상태)를 작은 고정 크기 블록에 나눠 저장해서, GPU 메모리가 패딩과 단편화로 버려지지 않게 합니다. 연속 배칭은 새 요청이 지금 배치가 끝나기를 기다리지 않고 생성 단계 사이사이에 배치에 끼어들게 해 줍니다. 이 둘 덕분에 vLLM을 돌리는 GPU 한 장은, 같은 GPU가 요청을 하나씩 처리할 때보다 훨씬 많은 동시 사용자를 감당합니다.</p>
<p>대가는 vLLM이 카드를 자기 것처럼 쓴다는 점입니다. 시작할 때 GPU 메모리에서 자기 몫을 차지하고, 가중치가 쓰고 남은 공간은 KV 캐시로 채우고, 호출하는 사람이 있든 없든 그 전부를 쥐고 있습니다. 모델도 Ollama가 쓰는 GGUF 파일이 아니라 Hugging Face 원본 가중치나 FP8 같은 GPU 친화적인 양자화를 원합니다.</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="#api%eb%8a%94-%ed%95%98%eb%82%98-%ec%84%9c%eb%b2%84%eb%8a%94-%eb%91%98" 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"># 노트북:   LLM_URL=http://localhost:11434/v1  LLM_MODEL=qwen3:8b</span>
</span></span><span class="line"><span class="cl"><span class="c1"># GPU 서버: 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>큰 GPU가 없는 제 노트북에서는 앱이 Ollama를 가리킵니다. 서버에서는 vLLM을 가리킵니다. 그 밖에는 아무것도 바뀌지 않고, GPU 머신을 아예 켜지 않고도 앱을 개발할 수 있습니다. vLLM 쪽에서는 <code>--served-model-name</code>으로 모델에 짧은 이름을 붙여 두면, 클라이언트가 Hugging Face 전체 경로를 보낼 필요가 없습니다.</p>
<p>잊기 쉬운 거라 하나 짚어 두겠습니다. 같은 모델이라도 두 서버에서 같은 답을 내지는 않습니다. Ollama의 <code>qwen3:8b</code>는 4비트 양자화본이고, vLLM에서는 보통 16비트나 FP8 가중치를 돌립니다. 한쪽에 맞춰 다듬은 프롬프트가 다른 쪽에서는 다르게 동작할 수 있으니, 답변 품질이든 거절이든 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="#ollama-%ec%84%a4%ec%b9%98-%eb%b0%a9%eb%b2%95" 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/ko/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-%ec%84%a4%ec%b9%98-%eb%b0%a9%eb%b2%95" aria-label="앵커">#</a>
    </span>
    
</h2>
<p>NVIDIA GPU와 최신 드라이버를 갖춘 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">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="#docker%eb%a1%9c-vllm-%eb%9d%84%ec%9a%b0%ea%b8%b0" 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"># 태그를 고정할 것. 이미지는 약 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에 공유 메모리가 필요함</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"># LAN이 아니라 ssh -L로 접속</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>태그는 고정하세요. vLLM은 빠르게 바뀌고 릴리스마다 플래그와 기본값이 달라지니, 그 변화를 언제 받아들일지는 내가 정해야 합니다. <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="#vllm-%ec%9a%b4%ec%98%81%ed%95%98%eb%a9%b4%ec%84%9c-%eb%b0%9c%eb%aa%a9-%ec%9e%a1%ed%9e%8c-%ea%b2%83%eb%93%a4" aria-label="앵커">#</a>
    </span>
    
</h2>
<p><strong>시작하는 데 몇 분이 걸립니다.</strong> 가중치 로드, 컴파일, CUDA 그래프 캡처까지 제 서버에서 190초가 걸렸고, 큰 모델이라면 3~5분이 보통입니다. 웹 앱에 맞춰 짠 헬스 체크는 준비가 끝나기도 전에 vLLM을 죽여 버립니다. Compose에서는 긴 <code>start_period</code>가, Kubernetes에서는 startup probe가 필요하다는 뜻입니다. 재시작할 때마다 몇 분씩 모델이 없는 상태가 된다는 뜻이기도 하니, 재시작을 가볍게 하면 안 됩니다.</p>
<p><strong>GPU 메모리는 시작할 때 준 만큼 가져갑니다.</strong> <code>--gpu-memory-utilization</code>은 vLLM이 쓸 수 있는 카드의 비율로 최근 버전의 기본값은 0.92이고, 가중치가 쓰고 남은 공간은 KV 캐시가 됩니다. vLLM이 카드를 혼자 쓴다면 문제없습니다. 제 경우는 옆에서 작은 모델들이 같이 돌아서 혼자가 아니라, 96 GB 카드에 0.70을 줬습니다. 모델은 FP8로 된 35B MoE(mixture-of-experts)였는데, 로드하고 나니 47.7 GiB를 차지했고 KV 캐시로 15 GiB가 남았습니다. 16k 컨텍스트라면 넉넉한 양이죠. 최대 시퀀스 길이가 KV 캐시에 들어가지 않는다며 vLLM이 시작을 거부하면, 더 큰 GPU를 찾아보기 전에 <code>--max-model-len</code>부터 낮춰 보세요.</p>
<p><strong>LoRA 슬롯은 메모리를 미리 잡아 둡니다.</strong> vLLM은 베이스 모델 하나에 LoRA 어댑터 여러 개를 얹어 서빙하고, 요청마다 모델 이름으로 어댑터를 바꿀 수 있습니다. Ollama로는 사실상 못 하는 일이죠. 그런데 시작할 때 <code>--max-lora-rank</code> 기준으로 <code>--max-loras</code>개만큼 어댑터 자리를 예약하고, MoE 모델에서는 그 예약이 전문가(expert)들까지 덮기 때문에 금방 불어납니다. 0.70에서 랭크 64 어댑터 네 개는 들어가지 않았습니다. 두 개는 들어갔습니다.</p>
<p><strong>기본 시퀀스 한도가 하이브리드 모델과 맞지 않았습니다.</strong> 제가 서빙하는 모델은 어텐션 사이에 Mamba 계열 레이어가 섞여 있고, 시퀀스마다 그 레이어용 캐시 블록이 따로 필요합니다. 그 카드에서 vLLM 기본 <code>--max-num-seqs</code>로는 시작에 실패했고, <code>--max-num-seqs 256</code>으로 해결됐습니다. 새로운 아키텍처의 모델이 뜨지 않는다면, 더 특이한 원인을 찾기 전에 이 플래그부터 확인해 보세요.</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이 들어 있으니 해석하지 못하고 죽었습니다. 그것도 당연히 모델을 전부 로드한 다음에요. 그래서 시도할 때마다 3분씩 날아갔습니다. 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"># 이전 pod가 먼저 GPU를 놓아야 함</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"># Service가 넣는 VLLM_PORT=tcp://... 막기</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"># 로드할 때는 서빙할 때보다 호스트 RAM이 훨씬 많이 듦</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"># 모델이 다 올라오기 전에는 판정하지 않음</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"># 재시작은 곧 전체 재로드이니 느긋하게</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는 2분을 기다린 다음, 모델이 올라올 때까지 15분을 더 주고 나서야 Kubernetes가 죽었다고 판단하게 합니다. liveness probe는 5분쯤 실패가 이어져야 재시작합니다. 바쁜 엔진에서는 <code>/health</code> 응답이 느릴 수 있고, 어차피 재시작하면 전체를 다시 로드해야 하니까요. GPU가 한 장일 때는 <code>Recreate</code>가 중요합니다. 롤링 업데이트는 새 pod를 먼저 띄우는데, 그 pod는 이전 pod가 아직 쥐고 있는 GPU를 기다리며 마냥 서 있게 됩니다. 메모리 제한이 넉넉해 보인다면, 실제로 넉넉하게 잡은 겁니다. 가중치가 GPU에 올라가고 나면 호스트 RAM은 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-%ec%93%b8-%eb%95%8c-%ec%95%8c%ec%95%84-%eb%91%98-%ea%b2%83" aria-label="앵커">#</a>
    </span>
    
</h2>
<p>놀랄 일은 적습니다. 대체로 하는 일 자체가 적어서죠.</p>
<p>한동안 조용하다가 들어온 첫 요청은 느립니다. 모델이 내려가 있어서 다시 로드해야 하기 때문입니다. <code>OLLAMA_KEEP_ALIVE</code>는 마지막 요청 이후 모델이 메모리에 머무는 시간을 정합니다. 그 멈칫거림이 거슬리면 값을 늘리고, 다른 일을 하지 않는 기계라면 <code>-1</code>로 두세요.</p>
<p>컨텍스트 윈도우는 GPU 메모리가 얼마나 있느냐에 따라 달라집니다. 24 GiB 미만이면 Ollama의 기본값은 4k 토큰으로, 대부분의 모델이 지원하는 길이보다 한참 짧습니다. 게다가 들어가지 않는 프롬프트는 거부되지 않고 조용히 잘립니다. 긴 프롬프트의 앞부분을 답변이 무시하는 것 같다면, <code>OLLAMA_CONTEXT_LENGTH</code>(또는 요청별 <code>num_ctx</code>)를 실제로 필요한 만큼 설정하세요.</p>
<p>원격 Ollama를 노트북으로 포워딩한다면 11434에 두지 마세요. Ollama의 기본 포트라서, 로컬 Ollama가 한 번이라도 뜨면 둘이 그 포트를 두고 경쟁하고, 모르는 사이에 엉뚱한 쪽과 이야기하고 있을 수 있습니다. 저는 11435로 포워딩합니다.</p>
<p>그리고 Ollama는 다중 사용자 서버가 아닙니다. 몇 명이 쓰는 내부 도구라면 괜찮습니다. 진짜 동시 요청이 몰리기 시작하면, 그때가 vLLM으로 옮길 시점입니다.</p>

<h2 class="relative group">GPU 메모리는 얼마나 필요할까
    <div id="gpu-메모리는-얼마나-필요할까" 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="#gpu-%eb%a9%94%eb%aa%a8%eb%a6%ac%eb%8a%94-%ec%96%bc%eb%a7%88%eb%82%98-%ed%95%84%ec%9a%94%ed%95%a0%ea%b9%8c" aria-label="앵커">#</a>
    </span>
    
</h2>
<p>가중치만 놓고 본 대략적인 크기입니다.</p>
<table>
	<thead>
			<tr>
					<th>모델 크기</th>
					<th>16비트 (BF16)</th>
					<th>8비트 (FP8)</th>
					<th>4비트 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비트에서는 파라미터당 약 2바이트, 8비트에서는 1바이트, Q4_K_M에서는 0.5바이트를 조금 넘습니다. 가중치 위에 KV 캐시가 더해지는데, 이건 컨텍스트 길이와 동시에 처리 중인 요청 수에 따라 커집니다. 같은 모델이라도 vLLM으로 50명을 서빙할 때가 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="#%ec%96%b4%eb%8a%90-%ec%aa%bd%ec%9d%84-%ec%93%b8%ea%b9%8c" aria-label="앵커">#</a>
    </span>
    
</h2>
<p>내 기계에서 모델을 돌리고 싶은 거라면 Ollama를 설치하고 거기서 멈추세요. 10분이면 답하는 모델이 생깁니다. GPU 서버가 있고 하루 종일 호출할 사용자나 앱이 있다면 vLLM을 쓰고, 느린 시작과 잡아먹는 메모리를 받아들이고, probe를 제대로 잡으세요. 결국 둘 다 쓰게 되는 경우도 많은데, 저도 그렇습니다. 코드를 쓰는 곳에는 Ollama, 요청이 도착하는 곳에는 vLLM.</p>
<p>다른 선택지도 있습니다. SGLang이 vLLM의 대안으로 흔히 꼽히지만 프로덕션에서 돌려 본 적이 없어서, 짐작으로 쓰느니 빼 두었습니다. 그리고 모델이 폰에서 돌아야 한다면 다시 llama.cpp인데, 이건 <a href="https://jared.lynskey.co.nz/ko/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>
