목표와 현재 상태

동일한 single-node k3s와 RTX 4090에서 vLLM과 Ollama를 각각 단독 실행해 model 준비 시간, API 응답 시간, GPU 사용량을 비교한다.

구분상태확인 내용
vLLM warm-cache 재기동완료Pod Ready까지 188초
vLLM API 5회완료HTTP 200 5회
vLLM·GPU metric완료요청 종료 후 상태와 DCGM 시점값 확인
Ollama 단독 배포·측정완료HTTP 200 5회, native timing, GPU 적재와 DCGM 시점값 확인
vLLM 복구완료rollout 성공, /health/v1/models 정상 확인
최종 engine 선택보류latency와 GPU memory 중 우선순위를 정한 뒤 결정

1. vLLM 최적화

기존 gemma4-model-cache PVC를 유지한 상태에서 Deployment를 재기동했다.

SECONDS=0
sudo k3s kubectl -n gemmaforge rollout restart deployment/gemma4-12b
sudo k3s kubectl -n gemmaforge rollout status \
  deployment/gemma4-12b \
  --timeout=40m
printf 'vLLM Pod Ready까지 %s초\n' "$SECONDS"

새 Pod의 생성 시각, Ready 전환 시각, imageID와 restart 수를 확인했다.

sudo k3s kubectl -n gemmaforge get pod \
  -l app.kubernetes.io/name=gemma4-vllm \
  -o json \
  | jq '.items[]
    | {
        pod: .metadata.name,
        created: .metadata.creationTimestamp,
        ready_at: ([.status.conditions[]
          | select(.type == "Ready")
          | .lastTransitionTime][0]),
        imageID: .status.containerStatuses[0].imageID,
        restarts: .status.containerStatuses[0].restartCount
      }'
항목해석
명령 실행 기준 Ready 시간188초rollout restart부터 Ready 확인까지 약 3분 8초
Kubernetes 시각 차이181초created부터 ready_at까지 약 3분 1초
두 측정값 차이7초restart 명령 처리와 상태 관찰 구간이 포함된 차이
Podgemma4-12b-5d4d7c54d5-vqbs2새 ReplicaSet에서 생성
restart0container 재시작 없이 정상 기동
imageIDsha256:251eba...814f이후 engine 비교에서 같은 vLLM artifact 식별값으로 사용

동일한 Pod 정보가 두 번 전달됐으며 두 결과는 완전히 같다. 결과 해석에는 한 Pod의 단일 기동 기록으로 사용한다.

2. vLLM API 기준선

2-1. Service 연결과 model 확인

Terminal A에서 Service를 WSL host의 8100에 연결했다.

sudo k3s kubectl -n gemmaforge port-forward \
  service/gemma4-vllm \
  8100:8000

Terminal B에서 health와 model 목록을 확인했다.

curl -fsS http://127.0.0.1:8100/health
curl -fsS http://127.0.0.1:8100/v1/models | jq
key
idgemma-4-12b-q4
owned_byvllm
rootgoogle/gemma-4-12B-it-qat-w4a16-ct
max_model_len8192
allow_samplingtrue
allow_viewtrue
allow_fine_tuningfalse

2-2. 동일 prompt 5회

재기동 후 첫 요청과 이후 warm 요청을 구분하기 위해 같은 prompt를 5회 실행했다.

for i in $(seq 1 5); do
  curl -sS \
    -o "/tmp/gemmaforge-06-vllm-${i}.json" \
    -w "engine=vllm run=${i} http=%{http_code} total=%{time_total}s\n" \
    http://127.0.0.1:8100/v1/chat/completions \
    -H 'Content-Type: application/json' \
    -d '{
      "model": "gemma-4-12b-q4",
      "messages": [
        {
          "role": "user",
          "content": "Kubernetes의 장점을 세 가지로 요약해줘."
        }
      ],
      "max_tokens": 128,
      "temperature": 0
    }'
done

응답 파일에서 model, 종료 사유와 token 수를 추렸다.

jq -s '[
  .[] |
  {
    model,
    finish_reason: .choices[0].finish_reason,
    prompt_tokens: .usage.prompt_tokens,
    completion_tokens: .usage.completion_tokens
  }
]' /tmp/gemmaforge-06-vllm-*.json
구분결과
HTTP 성공5/5, 모두 200
run 12.846273초
run 2~5 평균1.635251초
전체 5회 평균1.877455초
run 1 / warm 평균1.74배
run 1 추가 지연warm 평균 대비 약 74.1%
요청당 prompt token26
요청당 completion token128
종료 사유5회 모두 length

첫 요청은 이후 네 요청보다 느렸고, run 2부터는 약 1.63~1.64초 범위로 안정됐다. 다만 모든 응답이 max_tokens: 128에 도달해 finish_reason: length로 끝났으므로 완전한 문장 종료 시간이라기보다 128 token 생성 조건의 성능으로 해석해야 한다.

3. vLLM과 GPU metric

3-1. vLLM 요청 상태

API 요청을 마친 직후 vLLM metric을 확인했다.

curl -fsS http://127.0.0.1:8100/metrics \
  | grep -E '^vllm:(num_requests_running|num_requests_waiting|kv_cache_usage_perc|prompt_tokens_total|generation_tokens_total)'
metric해석
num_requests_running0측정 시점에 실행 중인 요청 없음
num_requests_waiting0대기 요청 없음
waiting: capacity0처리 용량 때문에 대기한 요청 없음
waiting: deferred0지연 처리된 요청 없음
kv_cache_usage_perc0요청 종료 후 KV cache 사용률 0%
prompt_tokens_total13026 × 5, 측정 요청과 일치
generation_tokens_total640128 × 5, 측정 요청과 일치

3-2. Prometheus DCGM 시점값

Terminal C에서 Prometheus Service를 WSL host의 9090에 연결했다.

sudo k3s kubectl -n monitoring port-forward \
  service/kube-prometheus-stack-prometheus \
  9090:9090

Terminal B에서 VRAM 사용량, GPU 사용률과 온도를 조회했다.

curl -fsSG http://127.0.0.1:9090/api/v1/query \
  --data-urlencode 'query=DCGM_FI_DEV_FB_USED' \
  | jq '.data.result[] | {metric: .metric, value: .value[1]}'
curl -fsSG http://127.0.0.1:9090/api/v1/query \
  --data-urlencode 'query=DCGM_FI_DEV_GPU_UTIL' \
  | jq '.data.result[] | {metric: .metric, value: .value[1]}'
curl -fsSG http://127.0.0.1:9090/api/v1/query \
  --data-urlencode 'query=DCGM_FI_DEV_GPU_TEMP' \
  | jq '.data.result[] | {metric: .metric, value: .value[1]}'
항목해석
VRAM 사용량22,080 MiBRTX 4090 24,564 MiB의 약 89.9%
계산상 잔여 VRAM2,484 MiB동일 GPU에 다른 대형 model을 함께 적재하기 어려운 수준
GPU 사용률4%요청이 끝난 뒤 조회한 시점값
GPU 온도47°C요청이 끝난 뒤 조회한 시점값

이 쿼리는 instant query이므로 22,080 MiB, 4%, 47°C를 실험 구간의 최댓값으로 해석하지 않는다. Ollama와 비교할 때도 같은 시점 또는 max_over_time을 사용해야 한다.

4. Ollama 첫 배포 실패와 vLLM GPU 해제

4-1. vLLM이 실행 중인 상태에서 발생한 Scheduling 실패

Ollama Deployment를 적용할 때 기존 vLLM Pod를 먼저 종료하지 않아 두 workload가 single-node의 같은 GPU와 memory를 동시에 요청했다. Scheduler는 Ollama Pod를 배치할 수 없다고 판단했다.

항목해석
가용 node0/1single-node에서 Ollama를 배치할 node가 없음
memoryInsufficient memory당시 기존 workload를 포함한 요청량 기준으로 Ollama의 memory request를 수용하지 못함
GPUInsufficient nvidia.com/gpu단일 nvidia.com/gpu: 1이 vLLM에 이미 할당된 상태
preemptionNo preemption victims foundScheduler가 축출해 자원을 확보할 수 있는 대상이 없다고 판단
결과Pendingimage나 container 시작 이전의 Scheduling 단계에서 중단

이 실패는 Ollama 자체의 GPU runtime 기동 오류가 아니라 Kubernetes Scheduler의 자원 할당 실패다. 동일한 RTX 4090 한 장으로 engine을 비교하려면 vLLM과 Ollama를 동시에 실행하지 않고, 이전 engine의 Pod 삭제와 GPU 해제를 확인한 뒤 다음 engine을 배포해야 한다.

4-2. vLLM scale-down과 해제 직후 상태

실패를 확인한 뒤 vLLM Deployment를 0 replica로 축소하고 기존 Pod 삭제를 기다렸다. 다음은 사용자가 전달한 scale-down, Pod 상태와 nvidia-smi output이다.

항목해석
vLLM scaledeployment.apps/gemma4-12b scaledvLLM Deployment를 0 replica로 변경
vLLM Pod 삭제condition met기존 gemma4-12b-5d4d7c54d5-vqbs2 삭제 조건 충족
Ollama Pod0/1 Pending이 캡처 시점에는 아직 node에 배치되지 않음
Ollama node<none>Scheduling 완료 전이므로 실제 배치 node 없음
nominated nodemomindesktopScheduler가 후보 node로 기록했지만 실제 배치를 의미하지 않음
GPU memory3,505 / 24,564 MiBWSL·display·driver를 포함할 수 있는 host 시점값
compute process없음이 시점에는 nvidia-smi에서 vLLM compute process가 관찰되지 않음
GPU 온도50°C자원 해제 직후 host 시점값

nvidia-smi의 전체 memory 사용량만으로 vLLM process가 남았다고 판단하지 않는다. 전달된 process 표에는 실행 중인 compute process가 없으므로 vLLM container의 GPU process는 해제된 상태로 본다. 다만 Ollama Pod는 여전히 Pending이므로 이 결과만으로 Ollama가 정상 기동됐다고 처리하지 않는다. 다음 Scheduler 재시도에서 Pod가 배치되는지 rollout과 새 event를 확인해야 한다.

vLLM 기준선 중간 결론

판단 항목현재 결과
vLLM 기동 안정성새 Pod가 restart 없이 Ready
warm-cache Pod Ready188초
API 성공률5/5, 100%
첫 요청2.846273초
이후 warm 요청평균 1.635251초
응답 제한5회 모두 128 token에 도달해 length 종료
요청 종료 후 대기열running 0, waiting 0
GPU memory시점값 22,080 MiB
Ollama 최초 배포vLLM 점유 상태에서 Insufficient memory, Insufficient nvidia.com/gpu
vLLM 해제 후 상태vLLM Pod 삭제 완료, compute process 없음
engine 선택비교와 vLLM 복구 완료, 최종 우선순위 결정 전까지 보류

vLLM은 동일 prompt 다섯 번을 모두 정상 처리했고, 첫 요청 이후 응답 시간이 안정됐다. Ollama의 첫 배포 실패를 통해 single-node·single-GPU 환경에서는 두 engine의 Deployment가 동시에 nvidia.com/gpu: 1을 요청할 수 없다는 점도 확인했다. 이 중간 결론 이후 수행한 Ollama 측정 결과는 아래 7~11절에 이어서 기록한다.

Ollama 측정 계획

vLLM 종료와 GPU compute process 해제 이후 Ollama Pod의 재스케줄과 rollout 상태 확인부터 측정을 이어가는 계획이었다. 아래 명령의 실행 결과는 7절 이후에 기록되어 있다.

sudo k3s kubectl -n gemmaforge get pod \
  -l app.kubernetes.io/name=gemma4-ollama \
  -o wide
sudo k3s kubectl -n gemmaforge rollout status \
  deployment/gemma4-ollama \
  --timeout=20m

계속 Pending이면 최신 event를 다시 확인한다.

sudo k3s kubectl -n gemmaforge describe pod \
  -l app.kubernetes.io/name=gemma4-ollama
sudo k3s kubectl -n gemmaforge get events \
  --sort-by=.lastTimestamp \
  | tail -n 50

Ollama가 Running·Ready로 전환된 뒤 다음 내용을 실행 순서에 맞춰 측정했다.

  1. Ollama server Ready 시간과 imageID
  2. gemma4:12b-it-q4_K_M pull 결과와 digest
  3. OpenAI-compatible API와 native API 5회
  4. DCGM 시점값
  5. vLLM·Ollama 중간 비교

7. Ollama server 준비 시간과 image 확인

sudo k3s kubectl -n gemmaforge get \
  pod,pvc,service \
  -l app.kubernetes.io/part-of=gemmaforge \
  -o wide
sudo k3s kubectl -n gemmaforge get pod \
  -l app.kubernetes.io/name=gemma4-ollama \
  -o json \
  | jq '.items[]
    | {
        pod: .metadata.name,
        created: .metadata.creationTimestamp,
        ready_at: ([.status.conditions[]
          | select(.type == "Ready")
          | .lastTransitionTime][0]),
        image: .spec.containers[0].image,
        imageID: .status.containerStatuses[0].imageID,
        restarts: .status.containerStatuses[0].restartCount
      }'
항목해석
Pod 상태1/1 RunningOllama container가 정상 기동하고 readiness를 통과
Pod IP·node10.42.0.191, momindesktopsingle-node k3s에 정상 배치
PVCollama-model-cache, 40Gi, Boundmodel cache 저장 공간 연결 완료
Servicegemma4-ollama:11434cluster 내부 Ollama API endpoint 생성
생성→Ready857초05:27:06부터 05:41:23까지 14분 17초
imageollama/ollama:0.32.1비교에 사용한 Ollama version 고정
imageIDsha256:6345f...51b0실제 실행 image 식별값
restart0container 재시작 없이 Ready

857초에는 앞서 발생한 vLLM GPU 점유와 Scheduler 대기 시간이 포함되어 있으므로 순수한 Ollama container 시작 시간으로 해석하지 않는다.

8. Ollama API 접속과 model pull

Terminal A에서 Ollama Service를 WSL host의 8114에 연결했다.

sudo k3s kubectl -n gemmaforge port-forward \
  service/gemma4-ollama \
  8114:11434

Terminal B에서 server version을 확인했다.

curl -fsS http://127.0.0.1:8114/api/version | jq
SECONDS=0
curl -fsS http://127.0.0.1:8114/api/pull \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "gemma4:12b-it-q4_K_M",
    "stream": false
  }' | jq
printf 'Ollama model pull에 %s초\n' "$SECONDS"
curl -fsS http://127.0.0.1:8114/api/tags \
  | jq '.models[]
    | select(.name == "gemma4:12b-it-q4_K_M")
    | {
        name,
        digest,
        size,
        format: .details.format,
        family: .details.family,
        parameter_size: .details.parameter_size,
        quantization_level: .details.quantization_level
      }'
항목해석
Ollama version0.32.1Deployment image tag와 server 응답이 일치
pull 상태successmodel artifact 다운로드 완료
pull 시간90초Ollama cache에 artifact를 저장한 실제 소요 시간
modelgemma4:12b-it-q4_K_M이후 API 요청에 사용할 정확한 model 이름
digest4eb23e...b05c비교 결과를 재현할 model artifact 식별값
size7,556,508,396 bytesregistry에 기록된 artifact 크기
formatggufvLLM Compressed Tensors checkpoint와 다른 형식
parameter size11.9BGemma 4 12B 계열
quantizationQ4_K_MOllama 비교에 사용한 Q4 quantization

model pull과 /api/tags 조회가 모두 성공해 이후 요청은 정확한 artifact 이름과 digest를 기준으로 실행됐다.

9. Ollama OpenAI-compatible API 5회 측정

for i in $(seq 1 5); do
  curl -sS \
    -o "/tmp/gemmaforge-06-ollama-openai-${i}.json" \
    -w "engine=ollama run=${i} http=%{http_code} total=%{time_total}s\n" \
    http://127.0.0.1:8114/v1/chat/completions \
    -H 'Content-Type: application/json' \
    -d '{
      "model": "gemma4:12b-it-q4_K_M",
      "messages": [
        {
          "role": "user",
          "content": "Kubernetes의 장점을 세 가지로 요약해줘."
        }
      ],
      "max_tokens": 128,
      "temperature": 0
    }'
done
jq -s '[
  .[] |
  {
    model,
    finish_reason: .choices[0].finish_reason,
    prompt_tokens: .usage.prompt_tokens,
    completion_tokens: .usage.completion_tokens
  }
]' /tmp/gemmaforge-06-ollama-openai-*.json
구분결과해석
HTTP 성공5/5, 모두 200OpenAI-compatible endpoint가 모든 요청을 정상 처리
run 157.345420초최초 model load와 초기화가 포함된 cold request
run 2~5 평균1.865693초model 적재 후 warm request 평균
warm 최소1.839423초run 4
warm 최대1.891809초run 2
run 1 / warm 평균30.74배Ollama 최초 요청에서 큰 cold-start 비용 확인
요청당 prompt token29OpenAI-compatible 응답 기준
요청당 completion token128설정한 max_tokens까지 생성
종료 사유5회 모두 length완전한 문장 종료가 아니라 token 제한 도달

Ollama도 동일 OpenAI-compatible 요청을 모두 성공했지만 첫 요청은 warm 요청보다 크게 느렸다. vLLM의 첫 요청 2.846273초와 직접 비교할 때는 Ollama run 1에 model load가 포함됐다는 차이를 함께 고려해야 한다.

10. Ollama native timing 5회 측정

curl -fsS http://127.0.0.1:8114/api/generate \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "gemma4:12b-it-q4_K_M",
    "keep_alive": 0
  }' | jq
for i in $(seq 1 5); do
  curl -fsS http://127.0.0.1:8114/api/chat \
    -H 'Content-Type: application/json' \
    -d '{
      "model": "gemma4:12b-it-q4_K_M",
      "messages": [
        {
          "role": "user",
          "content": "Kubernetes의 장점을 세 가지로 요약해줘."
        }
      ],
      "stream": false,
      "think": false,
      "keep_alive": -1,
      "options": {
        "temperature": 0,
        "num_predict": 128,
        "num_ctx": 8192
      }
    }' > "/tmp/gemmaforge-06-ollama-native-${i}.json"
 
  jq -r --arg run "$i" '[
    $run,
    (.total_duration / 1000000000),
    (.load_duration / 1000000000),
    .prompt_eval_count,
    .eval_count,
    (
      if .eval_duration > 0
      then (.eval_count / (.eval_duration / 1000000000))
      else null
      end
    ),
    .done_reason
  ] | @tsv' "/tmp/gemmaforge-06-ollama-native-${i}.json"
done

같은 input을 한 번 더 실행해 warm 상태를 재측정했다.

for i in $(seq 1 5); do
  curl -fsS http://127.0.0.1:8114/api/chat \
    -H 'Content-Type: application/json' \
    -d '{
      "model": "gemma4:12b-it-q4_K_M",
      "messages": [
        {
          "role": "user",
          "content": "Kubernetes의 장점을 세 가지로 요약해줘."
        }
      ],
      "stream": false,
      "think": false,
      "keep_alive": -1,
      "options": {
        "temperature": 0,
        "num_predict": 128,
        "num_ctx": 8192
      }
    }' > "/tmp/gemmaforge-06-ollama-native-${i}.json"
 
  jq -r --arg run "$i" '[
    $run,
    (.total_duration / 1000000000),
    (.load_duration / 1000000000),
    .prompt_eval_count,
    .eval_count,
    (
      if .eval_duration > 0
      then (.eval_count / (.eval_duration / 1000000000))
      else null
      end
    ),
    .done_reason
  ] | @tsv' "/tmp/gemmaforge-06-ollama-native-${i}.json"
done
측정run 1 totalrun 1 load이후 totalgeneration 처리량해석
1차5.595056초3.897097초run 2~5 평균 1.880014초80.11~83.22 token/sunload 직후 첫 요청에 model load 포함
2차1.962981초0.248694초5회 평균 1.843276초83.71~84.10 token/smodel이 warm한 상태에서 안정적으로 반복

native API는 OpenAI-compatible client 전체 시간과 달리 load_duration과 generation 처리량을 분리해 보여준다. 1차 run 1에서 총 5.595056초 중 약 3.897097초가 load에 사용됐고, 이후에는 약 1.8~1.9초80~84 token/s 범위로 안정됐다.

11. Ollama GPU 적재와 DCGM metric 확인

curl -fsS http://127.0.0.1:8114/api/ps \
  | jq '.models[]
    | {
        name,
        size,
        size_vram,
        context_length,
        quantization_level: .details.quantization_level
      }'
sudo k3s kubectl -n gemmaforge exec \
  deployment/gemma4-ollama \
  -- ollama ps
nvidia-smi \
  --query-compute-apps=pid,process_name,used_memory \
  --format=csv,noheader,nounits
curl -fsSG http://127.0.0.1:9090/api/v1/query \
  --data-urlencode 'query=DCGM_FI_DEV_FB_USED' \
  | jq '.data.result[] | {metric: .metric, value: .value[1]}'
curl -fsSG http://127.0.0.1:9090/api/v1/query \
  --data-urlencode 'query=DCGM_FI_DEV_GPU_UTIL' \
  | jq '.data.result[] | {metric: .metric, value: .value[1]}'
curl -fsSG http://127.0.0.1:9090/api/v1/query \
  --data-urlencode 'query=DCGM_FI_DEV_GPU_TEMP' \
  | jq '.data.result[] | {metric: .metric, value: .value[1]}'
항목해석
/api/ps model size8,373,969,878 bytes실행 중인 model이 차지하는 전체 크기
/api/ps VRAM size8,373,969,878 bytesmodel 전체가 GPU memory에 적재됨
Ollama processor100% GPUCPU fallback 없이 RTX 4090 사용
context length8192vLLM의 max_model_len과 동일하게 설정
quantizationQ4_K_M비교 대상 artifact와 일치
host nvidia-smi process출력 없음WSL 환경의 host process 조회에서는 container process가 표시되지 않음
DCGM framebuffer memory12,353 MiBOllama Pod label로 수집된 시점값
DCGM GPU utilization13%요청 이후 instant query 시점값
DCGM GPU temperature44°C요청 이후 instant query 시점값

Ollama API와 ollama ps가 모두 model의 100% GPU 적재를 보여주고 DCGM metric도 exported_container="ollama"로 수집됐다. 따라서 nvidia-smi --query-compute-apps에 출력이 없더라도 CPU fallback으로 판단하지 않는다. 세 DCGM 값은 instant query 결과이므로 실험 구간의 최댓값이 아니라 조회 시점 상태다.

vLLM·Ollama 측정 결과 요약

항목vLLMOllama해석
실제 artifactgoogle/gemma-4-12B-it-qat-w4a16-ctgemma4:12b-it-q4_K_M, 4eb23e...b05cmodel 계열과 Q4급은 같지만 artifact format과 bytes는 다름
engine imageIDsha256:251eba...814fsha256:6345fb...51b0실제 실행 image 식별값 기록
Pod Ready188초857초Ollama 값에는 최초 Scheduling 실패와 대기 시간이 포함됨
OpenAI run 12.846273초57.345420초Ollama 최초 요청에는 model load·초기화 비용이 크게 반영
OpenAI run 2~5 평균1.635251초1.865693초이번 단일 요청 측정에서는 vLLM이 약 0.230442초 빠름
HTTP 성공5/55/5두 engine 모두 기능 검증 통과
completion token요청당 128요청당 128두 결과 모두 length로 종료
GPU framebuffer memory시점값 22,080 MiB시점값 12,353 MiBOllama가 약 9,727 MiB 적게 관측됨
GPU utilization시점값 4%시점값 13%요청 종료 후 서로 다른 instant query이므로 최고 사용률 비교가 아님
GPU temperature시점값 47°C시점값 44°C요청 종료 후 시점값
restart00두 engine 모두 측정 구간에서 container restart 없음
native generation별도 동등 지표 없음80~84 token/sOllama native API에서만 별도로 기록

13. Ollama 종료와 vLLM 복구

curl -fsS http://127.0.0.1:8114/api/generate \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "gemma4:12b-it-q4_K_M",
    "keep_alive": 0
  }' | jq
sudo k3s kubectl -n gemmaforge scale \
  deployment/gemma4-ollama \
  --replicas=0
sudo k3s kubectl -n gemmaforge wait \
  --for=delete \
  pod \
  -l app.kubernetes.io/name=gemma4-ollama \
  --timeout=5m
sudo k3s kubectl -n gemmaforge get pods -o wide
nvidia-smi
sudo k3s kubectl -n gemmaforge scale \
  deployment/gemma4-12b \
  --replicas=1
sudo k3s kubectl -n gemmaforge rollout status \
  deployment/gemma4-12b \
  --timeout=40m
curl -fsS http://127.0.0.1:8100/health

curl -f가 오류 없이 종료됐으므로 응답 본문이 없는 /health endpoint가 정상 상태 코드를 반환한 것으로 판단한다.

curl -fsS http://127.0.0.1:8100/v1/models | jq
확인 항목결과해석
Ollama unloaddone_reason: unload실행 중이던 model을 memory에서 해제
Ollama Deploymentreplicas=0 scale 성공Ollama serving workload 종료
Ollama Podnamespace에 Pod 없음GPU resource claim을 보유할 Pod가 제거됨
GPU 상태3,647 MiB / 24,564 MiB, compute process 없음Ollama 종료 후 GPU가 serving workload에서 해제됨
vLLM Deploymentreplicas=1 scale 성공기존 serving engine 재기동
vLLM rollout3분 30초, successfully rolled out새 vLLM Pod가 Ready 상태에 도달
/healthcurl -f 성공, 본문 없음vLLM health endpoint 정상
/v1/modelsgemma-4-12b-q4, max_model_len=8192기존 model이 다시 등록되고 API가 정상 복구됨

현재까지의 해석

이번 단일 요청 기준에서는 vLLM의 warm 응답이 평균 1.635251초로 Ollama의 1.865693초보다 약 12.3% 짧았다. 반면 DCGM framebuffer memory 시점값은 Ollama가 12,353 MiB로 vLLM의 22,080 MiB보다 약 44.1% 적었다.

하지만 이 결과를 순수한 serving engine 성능 차이라고 단정하지 않는다. vLLM은 Google의 Compressed Tensors QAT checkpoint를 사용했고 Ollama는 GGUF Q4_K_M artifact를 사용했으며, Ollama Pod Ready 시간에는 Scheduling 실패가 포함됐다. GPU 값도 구간 최댓값이 아닌 서로 다른 시점의 instant query다.

현재 증거로는 “warm 단일 요청 latency는 vLLM이 더 짧고, 조회 시점 GPU memory는 Ollama가 더 적다”까지 판단할 수 있다. 비교를 마친 뒤 Ollama를 정상 종료하고 vLLM rollout, health endpoint, model 목록을 모두 확인했으므로 기존 serving 상태로의 원복도 완료됐다.

남은 결정

실행 작업은 모두 끝났다. 이제 latency, GPU memory, 운영 편의성 가운데 무엇을 우선할지 정하고 최종 serving engine과 결과 해석 범위를 한 문장으로 남기면 Step 6을 마무리할 수 있다.