Skip to content

Split View: vLLM 내부 구조 (7) — 배포 튜닝과 흔한 함정, OOM 진단 순서

✨ Learn with Quiz
|

vLLM 내부 구조 (7) — 배포 튜닝과 흔한 함정, OOM 진단 순서

튜닝 순서를 먼저 정하기

여섯 편에 걸쳐 내부를 봤으니 마지막은 배포입니다. 다만 손잡이를 나열하기 전에 순서부터 정하고 시작하겠습니다. 튜닝이 실패하는 가장 흔한 이유는 잘못된 값을 골라서가 아니라 여러 값을 한꺼번에 바꿔서 무엇이 효과가 있었는지 모르게 되기 때문입니다.

권하는 순서는 이렇습니다. 첫째, 모델이 GPU에 들어가게 만듭니다. 둘째, 길이 한도를 워크로드에 맞춥니다. 셋째, KV 캐시 여유를 확보합니다. 넷째, 그 뒤에야 배치와 지연을 조절합니다. 앞의 세 단계가 안 잡힌 상태에서 배치 인자를 만지면 대개 아무 일도 일어나지 않습니다.

내용은 2026-08-12에 공식 문서·소스에서 확인했습니다. vLLM은 변화가 빠르니 설정값과 동작은 사용 중인 버전의 문서로 다시 확인하세요.

# 이 글에서 다루는 인자들이 모이는 전형적인 형태
vllm serve meta-llama/Llama-3.1-8B-Instruct \
  --max-model-len 8192 \
  --gpu-memory-utilization 0.92 \
  --tensor-parallel-size 1 \
  --max-num-seqs 128

gpu_memory_utilization — 가장 먼저 만지는 값

공식 문서의 정의는 이렇습니다. 모델 실행기가 쓸 GPU 메모리의 비율이고, 0과 1 사이의 값을 가집니다. 그리고 제가 2026-08-12에 문서와 main 브랜치 CacheConfig 소스에서 확인한 선언 기본값은 0.92였습니다. 오래된 글에는 다른 숫자가 적혀 있을 수 있으니 사용 중인 버전에서 직접 확인하세요.

이 값이 중요한 이유는 1편과 6편에서 본 구조 때문입니다. vLLM은 이 비율만큼의 메모리를 확보한 뒤, 거기서 가중치와 오버헤드를 뺀 나머지를 전부 KV 캐시로 씁니다. 즉 이 값을 올리면 곧바로 KV 캐시가 커지고, 더 긴 요청과 더 많은 동시 요청을 감당하게 됩니다.

그렇다고 1에 가깝게 올리면 되느냐 하면 그렇지 않습니다. 이 비율은 GPU 전체를 기준으로 계산되므로 같은 GPU를 쓰는 다른 프로세스가 있으면 계산이 어긋납니다. 쿠버네티스에서 GPU를 공유하는 구성이라면 특히 그렇습니다. 또 단편화나 일시적인 활성화 텐서 때문에 실사용은 계산보다 조금 더 나오므로, 여유를 0으로 만들면 평소에는 멀쩡하다가 특정 요청 조합에서만 터집니다.

값을 올려도 처리량이 안 늘어난다면 한도가 메모리가 아닌 다른 곳에 있다는 신호입니다. max_num_seqsmax_num_batched_tokens가 먼저 걸리고 있을 수 있습니다.

텐서 병렬과 파이프라인 병렬

여기서 가장 흔한 오해를 먼저 걷어내겠습니다. 텐서 병렬은 성능 옵션이 아니라 용량 옵션입니다.

공식 문서의 기준은 명확합니다. 모델이 GPU 한 장에는 안 들어가지만 한 노드의 여러 GPU에는 들어갈 때 텐서 병렬을 씁니다. 한 노드에도 안 들어갈 때 파이프라인 병렬을 함께 씁니다. 설정 방법도 문서가 직접 제시합니다. tensor_parallel_size를 노드당 GPU 수로, pipeline_parallel_size를 노드 수로 두는 것입니다. 문서가 든 예로 노드 2대에 GPU 8장씩이면 텐서 병렬 8, 파이프라인 병렬 2가 됩니다.

문서가 언급한 예외 상황도 알아 둘 만합니다. 한 노드 안에서 GPU 수가 균등하게 나눠지지 않는 경우에는 텐서 병렬을 1로 두고 파이프라인 병렬을 GPU 수로 두어 층을 불균등하게 쪼갤 수 있습니다.

tensor_parallel_size의 기본값은 문서에서 1로 확인했습니다. 즉 아무것도 안 주면 GPU 한 장을 씁니다. 서버에 GPU가 여러 장 꽂혀 있어도 그렇습니다. 멀티 GPU 장비에서 성능이 기대에 못 미친다면 이 값부터 확인하세요.

한 장에 이미 들어가는 모델에 텐서 병렬을 걸면 어떻게 될까요. 각 GPU가 자기 몫의 KV 캐시를 갖게 되어 총 캐시가 늘어나는 이득은 있지만, 층마다 GPU 사이 통신이 추가됩니다. 그래서 실측 없이 무조건 이득이라고 말할 수 없습니다. 4편에서 본 것처럼 공식 문서가 선점 대응책 목록에 텐서 병렬 상향을 올려 둔 이유는 처리 속도가 아니라 캐시 공간 때문입니다.

양자화 선택

양자화는 가중치를 더 적은 비트로 표현해 메모리를 줄입니다. 그리고 줄어든 만큼이 그대로 KV 캐시로 돌아갑니다. 6편의 구조를 떠올리면 이 이득이 왜 큰지 분명합니다. 같은 GPU에서 더 긴 컨텍스트와 더 많은 동시 요청을 얻습니다.

vLLM 문서가 다루는 방식은 여러 갈래입니다. AutoAWQ, BitsAndBytes, GPTQModel, Intel Neural Compressor, LLM Compressor, NVIDIA Model Optimizer, AMD Quark, TorchAO 등이 문서에 나열되어 있고, LLM Compressor 쪽은 FP8 W8A8, INT4 W4A16, INT8 W8A8 같은 조합을 다룹니다. 문서는 시작점으로 LLM Compressor를 안내합니다.

고를 때 실제로 중요한 것은 방식 이름이 아니라 두 가지입니다.

하드웨어 지원입니다. 문서는 양자화 구현마다 하드웨어 플랫폼 호환성이 다르다는 표를 제공하면서, 이 호환성 표가 계속 바뀔 수 있다고 명시합니다. 그러니 이름만 보고 고르지 말고 여러분의 GPU 세대에서 실제로 지원되는지 표를 확인하세요. 잘못 고르면 아예 안 뜨거나, 뜨더라도 가속을 못 받아 느려집니다.

이미 양자화된 모델인지 여부입니다. --quantization 인자의 설명은 가중치 양자화 방법을 지정한다는 것이고, 값을 주지 않으면 모델 설정 파일의 양자화 설정을 확인한다고 되어 있습니다. 즉 허브에서 이미 양자화된 체크포인트를 받아 쓴다면 대개 따로 지정하지 않아도 됩니다.

품질 이야기도 빼놓을 수 없습니다. 양자화는 공짜가 아니고 정확도에 영향을 줍니다. 얼마나 영향을 주는지는 모델과 방식과 과제에 따라 다릅니다. 그러니 여기서 "4비트는 괜찮다" 같은 일반화를 하지 않겠습니다. 여러분의 평가셋으로 직접 재는 것 말고 답이 없습니다.

KV 캐시 자료형

가중치만 줄일 수 있는 것이 아닙니다. KV 캐시 자체의 자료형도 설정 항목입니다. --kv-cache-dtype의 문서 설명은 KV 캐시 저장에 쓸 자료형이고, auto이면 모델의 자료형을 따른다는 것입니다. 기본값은 문서와 소스 모두에서 auto로 확인했습니다. 소스에는 CUDA 11.8 이상에서 fp8 계열을 지원한다는 설명이 함께 붙어 있습니다.

이 손잡이가 매력적인 이유는 효과가 정확히 KV 캐시에만 꽂히기 때문입니다. 가중치는 그대로 두고 캐시만 절반으로 줄이면, 같은 메모리로 길이나 동시성을 늘릴 수 있습니다. 다만 이것도 정밀도 손실이므로 평가 없이 켜지는 마세요.

OOM 진단 순서

메모리 부족은 vLLM 운영에서 가장 자주 만나는 문제입니다. 진단의 첫 단추는 언제나 하나입니다. 기동 중에 났는지 운영 중에 났는지를 먼저 가릅니다.

기동 중에 실패했다면 원인은 둘 중 하나입니다.

가중치가 안 들어간 경우입니다. 모델을 적재하는 단계에서 죽습니다. 해법은 양자화, 텐서 병렬, 더 작은 모델입니다. gpu_memory_utilization을 올리는 것은 큰 도움이 안 됩니다. 이미 대부분을 주고 있는데도 가중치가 안 들어가는 상황이기 때문입니다.

KV 캐시가 부족한 경우입니다. 6편에서 본 그 에러입니다. 최대 시퀀스 길이가 KV 캐시에 담을 수 있는 토큰 수보다 크다는 메시지가 나오고, 에러 자체가 해법을 알려 줍니다. gpu_memory_utilization을 올리거나 max_model_len을 줄이는 것입니다. 실무에서는 후자가 먼저입니다. 대개 실제 워크로드에 필요한 것보다 길게 열어 둔 상태이기 때문입니다.

운영 중에 났다면 이야기가 다릅니다. vLLM은 기동 시점에 캐시를 미리 잡아 두므로, 정상적인 상황이라면 요청이 늘어난다고 갑자기 OOM이 나지는 않습니다. 요청이 많아지면 OOM 대신 4편에서 본 선점이 일어납니다. 그래서 운영 중 OOM은 대개 vLLM 바깥에 원인이 있습니다. 같은 GPU에 다른 프로세스가 들어왔거나, gpu_memory_utilization이 실제 여유보다 크게 잡혀 있었거나, 평소에 안 나오던 크기의 멀티모달 입력이 들어온 경우입니다.

그리고 자주 오진되는 경우가 하나 더 있습니다. OOM은 아닌데 갑자기 느려지는 상황입니다. 이때는 메모리 인자를 만지기 전에 선점 경고 로그부터 확인하세요. 선점이 반복되고 있다면 그것은 성능 문제가 아니라 용량 문제입니다.

흔한 함정 다섯 가지

시리즈 전체에서 반복해 나온 함정들을 모아 둡니다.

함정결과관련 편
오래된 글의 설정을 그대로 복사제거된 인자를 쓰게 됨. 특히 스왑 관련 설정은 V1에서 빠졌습니다4편
gpu_memory_utilization을 한계까지 상향평소엔 멀쩡하다 특정 요청 조합에서만 터짐이 글
max_model_len을 모델 최대치로 개방아무도 안 쓰는 길이를 감당하느라 동시 처리 능력을 잃음6편
애플리케이션에서 생성 상한을 상수로 고정긴 입력이 들어오는 순간 요청이 거부됨6편
프롬프트 맨 앞에 타임스탬프나 세션 식별자접두사 캐시가 통째로 무효화. 인자로는 해결 불가5편

마지막 줄이 특히 중요합니다. 앞의 네 개는 설정을 고쳐 해결되지만, 이건 프롬프트 설계의 문제라 어떤 인자를 만져도 나아지지 않습니다.

일곱 편을 관통하는 결론은 하나입니다. vLLM에서 성능은 대체로 KV 캐시를 얼마나 아끼고 재사용하는가의 문제입니다. 블록으로 쪼개고, 스텝마다 다시 채우고, 겹치는 앞부분을 공유하고, 길이 한도로 최악의 경우를 제한하는 것까지 전부 같은 이야기의 다른 면입니다.

직접 해보기

참고 자료

Inside vLLM (7) — Deployment Tuning, Common Pitfalls, and OOM Triage

Deciding the Tuning Order First

Six parts have covered the internals, so this last part turns to deployment. Before listing out the knobs, though, the order needs to be fixed first. The most common reason tuning fails is not that someone picked a wrong value — it is that several values got changed at once, so there is no way to tell which one actually mattered.

The recommended order is this. First, get the model to fit on the GPU. Second, match the length limit to the workload. Third, secure headroom in the KV cache. Fourth, only after that, tune batching and latency. Touching the batching arguments before the first three steps are settled usually does nothing at all.

Everything here was verified against the official documentation and source on 2026-08-12. vLLM moves fast, so re-check settings and behavior against the documentation for the version you are running.

# A typical form combining the arguments covered in this post
vllm serve meta-llama/Llama-3.1-8B-Instruct \
  --max-model-len 8192 \
  --gpu-memory-utilization 0.92 \
  --tensor-parallel-size 1 \
  --max-num-seqs 128

gpu_memory_utilization — The First Value to Touch

The official documentation defines it this way: the fraction of GPU memory the model executor will use, a value between 0 and 1. The declared default confirmed in the documentation and in the CacheConfig source on the main branch, on 2026-08-12, was 0.92. Older posts may state a different number, so check it directly on the version you are running.

This value matters because of the structure covered in parts 1 and 6. vLLM reserves memory equal to this fraction, then uses everything left over after weights and overhead as KV cache. So raising this value immediately grows the KV cache, and the engine can then handle longer requests and more of them concurrently.

That does not mean pushing it close to 1 is the right move, though. This fraction is calculated against the entire GPU, so if another process is sharing the same GPU, the calculation goes wrong. This is especially true in a Kubernetes setup that shares a GPU. Also, actual usage tends to run a bit higher than the calculation because of fragmentation and transient activation tensors, so if you set that headroom to 0, things look fine most of the time and then blow up only on certain combinations of requests.

If raising this value does not increase throughput, that is a signal the limit lives somewhere other than memory. max_num_seqs or max_num_batched_tokens may be the one binding first.

Tensor Parallelism and Pipeline Parallelism

Start by clearing up the most common misconception here. Tensor parallelism is not a performance option. It is a capacity option.

The official documentation's criterion is clear. Use tensor parallelism when the model does not fit on one GPU but does fit across multiple GPUs on one node. Use pipeline parallelism alongside it when the model does not even fit on one node. The documentation also gives the setup directly: set tensor_parallel_size to the number of GPUs per node, and pipeline_parallel_size to the number of nodes. In the example the documentation gives, 2 nodes with 8 GPUs each becomes tensor parallelism 8 and pipeline parallelism 2.

The exception the documentation mentions is also worth knowing. When the GPUs within one node cannot be split evenly, you can set tensor parallelism to 1 and pipeline parallelism to the GPU count, letting the layers be split unevenly.

The default for tensor_parallel_size was confirmed as 1 in the documentation. In other words, if you give it nothing, the engine uses a single GPU — even if the server has several GPUs plugged in. If performance on a multi-GPU box falls short of expectations, check this value first.

What happens if you turn on tensor parallelism for a model that already fits on one GPU? Each GPU gets its own share of the KV cache, so total cache capacity grows — that part is a genuine benefit — but inter-GPU communication is added at every layer. So it cannot be called an unconditional win without actually measuring it. As seen in part 4, the reason the official documentation lists raising tensor parallelism among the countermeasures for preemption is cache space, not processing speed.

Choosing Quantization

Quantization reduces memory by representing weights in fewer bits. And whatever memory that frees up goes straight back into the KV cache. Recalling the structure from part 6 makes it clear why this benefit is large: on the same GPU, you get longer context and more concurrent requests.

The vLLM documentation covers several different approaches. AutoAWQ, BitsAndBytes, GPTQModel, Intel Neural Compressor, LLM Compressor, NVIDIA Model Optimizer, AMD Quark, and TorchAO are among those listed, and the LLM Compressor path covers combinations like FP8 W8A8, INT4 W4A16, and INT8 W8A8. The documentation points to LLM Compressor as the starting point.

What actually matters when choosing is not the name of the method — it comes down to two things.

Hardware support. The documentation provides a table showing that hardware platform compatibility differs by quantization implementation, and states explicitly that this compatibility table can keep changing. So do not pick by name alone — check the table for whether your GPU generation is actually supported. Pick wrong, and it either will not start at all, or it will start but run slower without getting any acceleration.

Whether the model is already quantized. The description of the --quantization argument is that it specifies the weight quantization method, and that if no value is given, it checks the quantization setting in the model's config file. In other words, if you are pulling an already-quantized checkpoint from the hub, you usually do not need to specify this separately.

Quality is also part of this, and it cannot be skipped. Quantization is not free — it affects accuracy. How much it affects accuracy depends on the model, the method, and the task. So this post will not make a generalization like "4-bit is fine" here. There is no substitute for measuring it yourself, on your own evaluation set.

KV Cache Data Type

Weights are not the only thing that can be shrunk. The data type of the KV cache itself is also a configuration item. The documentation describes --kv-cache-dtype as the data type to use for KV cache storage, and states that auto follows the model's data type. The default was confirmed as auto in both the documentation and the source. The source also notes that the fp8 family is supported on CUDA 11.8 and above.

What makes this knob appealing is that its effect lands exactly on the KV cache and nowhere else. Leave the weights alone and cut just the cache in half, and the same memory buys you more length or more concurrency. That said, this is also a precision loss, so do not turn it on without evaluating it first.

The OOM Diagnostic Order

Running out of memory is the single most common problem in operating vLLM. The first step in diagnosing it is always the same. First, work out whether it happened during startup or during operation.

If it failed during startup, the cause is one of two things.

The weights do not fit. It dies at the model-loading stage. The fixes are quantization, tensor parallelism, or a smaller model. Raising gpu_memory_utilization does not help much here, because the situation is already that the weights do not fit even though most of the memory is already being given to them.

The KV cache is too small. This is the same error seen in part 6. You get a message that the maximum sequence length is larger than the number of tokens the KV cache can hold, and the error itself states the fix: raise gpu_memory_utilization, or lower max_model_len. In practice, the latter comes first, because the length is usually left open wider than the actual workload needs.

If it happened during operation, the story is different. vLLM reserves the cache up front at startup, so under normal conditions, a spike in requests does not suddenly cause OOM. When requests pile up, what happens instead of OOM is the preemption covered in part 4. So an OOM during operation usually has a cause outside vLLM: another process landed on the same GPU, gpu_memory_utilization was set higher than the actual headroom, or an unusually large multimodal input came in that had never shown up before.

There is one more case that gets misdiagnosed often: a sudden slowdown that is not actually OOM. In this case, check the preemption warning logs before touching any memory argument. If preemption is happening repeatedly, that is a capacity problem, not a performance problem.

Five Common Pitfalls

Here is a collection of the pitfalls that came up repeatedly across the series.

PitfallResultRelated part
Copying settings straight from an old postYou end up using a removed argument. Swap-related settings in particular were dropped in V1Part 4
Pushing gpu_memory_utilization to the limitLooks fine most of the time, then blows up only on certain combinations of requestsThis post
Opening max_model_len to the model's maximumLosing concurrent capacity to support a length nobody actually usesPart 6
Hardcoding the generation cap as a constant in the applicationRequests get rejected the moment a long input arrivesPart 6
A timestamp or session identifier at the very front of the promptInvalidates the prefix cache entirely. Cannot be fixed with any argumentPart 5

The last row matters most. The first four get fixed by changing a setting. This one is a prompt-design problem, so no argument you touch makes it better.

One conclusion runs through all seven parts. In vLLM, performance mostly comes down to how carefully you conserve and reuse the KV cache. Splitting it into blocks, refilling it every step, sharing overlapping prefixes, and using length limits to bound the worst case — all of it is a different face of the same story.

Try It Yourself

References