Skip to content
Published on

vLLM 내부 구조 (6) — 컨텍스트 윈도우, max_model_len, max_tokens 완전 정리

공유하기
Authors

왜 이 다섯 개가 헷갈리는가

vLLM을 운영하면서 가장 많이 나오는 질문은 성능이 아니라 길이입니다. 컨텍스트 윈도우가 128k라는 모델을 올렸는데 왜 8192에서 막히는지, max_tokens를 4000으로 줬는데 왜 거부되는지, max_num_batched_tokens는 또 무엇인지.

헷갈리는 이유는 분명합니다. 이름이 전부 비슷하고, 서로 다른 곳에서 정해지며, 넘었을 때 나타나는 증상이 제각각이기 때문입니다. 어떤 것은 요청이 거부되고, 어떤 것은 조용히 응답이 잘리고, 어떤 것은 서버가 아예 안 뜹니다.

이 글은 그 다섯 개를 한 번에 정리합니다. 순서는 이렇습니다. 먼저 비교표로 전체 지도를 잡고, 각각을 설명한 뒤, 실제로 자주 겪는 세 가지 시나리오를 숫자로 따라가 보고, 마지막에 에러 메시지로 원인을 역추적하는 방법을 정리합니다.

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

한눈에 보는 비교표

이름정하는 곳제한하는 대상넘었을 때 생기는 일
컨텍스트 윈도우모델 자체 (모델 설정 파일)모델이 한 번에 볼 수 있는 토큰 수이보다 큰 엔진 설정을 요구하면 기동 단계에서 문제가 됩니다
max_model_len엔진 기동 인자요청 하나의 입력과 출력을 합친 길이요청이 거부됩니다
max_tokens (또는 max_completion_tokens)요청 본문그 요청이 새로 생성할 토큰 수생성이 그 지점에서 중단됩니다
max_num_batched_tokens엔진 기동 인자한 스텝에 배치 전체가 처리할 토큰 수요청이 여러 스텝으로 나뉘거나 밀립니다
max_num_seqs엔진 기동 인자한 스텝에 동시에 담을 시퀀스 수대기 큐에서 기다립니다

표에서 가장 중요한 줄은 두 번째입니다. max_model_len이 제한하는 것은 입력이 아니라 입력과 출력의 합계입니다. 공식 엔진 인자 문서가 이 값을 프롬프트와 출력을 합친 모델 컨텍스트 길이라고 명시하고 있습니다. 길이 관련 오해의 절반은 여기서 나옵니다.

컨텍스트 윈도우와 max_model_len

두 개는 층이 다릅니다.

컨텍스트 윈도우는 모델의 성질입니다. 학습 방식에서 나온 구조적 한계라서 설정 파일을 고쳐 늘릴 수 있는 성질이 아닙니다. 모델 카드에 128k라고 적힌 그 숫자입니다.

max_model_len은 엔진의 설정입니다. 공식 문서에 따르면 이 값을 지정하지 않으면 모델 설정에서 자동으로 유도됩니다. 즉 아무것도 안 주면 대개 모델의 컨텍스트 윈도우를 따라갑니다. 하지만 명시하면 그 값이 실제 한도가 됩니다.

여기서 첫 번째 오해가 풀립니다. 모델이 128k를 지원한다고 해서 여러분의 서버가 128k를 받는 것이 아닙니다. 받는 것은 max_model_len입니다. 그리고 이 값을 낮춰 두는 것은 실수가 아니라 대개 의도된 선택입니다. 이유는 뒤에서 다룹니다.

기본값을 여기서 숫자로 적지 않는 이유도 밝혀 둡니다. 공식 문서는 이 인자에 고정된 기본값을 제시하지 않고 모델 설정에서 유도된다고만 설명합니다. 그러니 여러분의 배포에서 실제로 몇으로 잡혔는지는 기동 로그로 확인하세요.

max_tokens와 이름이 바뀐 이야기

max_tokens는 요청 본문에 넣는 값이고, 그 요청이 새로 생성할 토큰 수의 상한입니다. 입력 길이는 여기 안 들어갑니다. 그래서 이 값 자체는 "얼마나 길게 답할지"를 정할 뿐, 얼마나 긴 질문을 받을지와는 무관합니다.

여기서 최신 정보를 하나 짚고 갑니다. main 브랜치의 채팅 완성 요청 정의를 보면 max_tokens 필드에 사용 중단 표시가 붙어 있고, max_completion_tokens를 쓰라는 안내가 달려 있습니다. 그리고 두 값이 모두 들어오면 max_completion_tokens가 우선합니다. OpenAI 호환 API의 흐름을 따라간 변화입니다. 새로 코드를 쓴다면 max_completion_tokens 쪽을 쓰는 편이 낫고, 기존 코드가 max_tokens를 쓰고 있다면 당장 깨지지는 않지만 언젠가 정리해야 할 부채로 두면 됩니다.

이 값을 아예 안 주면 어떻게 될까요. 남은 여유만큼 생성하다가 모델이 끝맺거나 한도에 닿을 때까지 갑니다. 서비스 관점에서는 상한을 명시하는 편이 거의 언제나 낫습니다. 상한이 없으면 사용자 한 명이 KV 캐시를 오래 붙들고 있게 되고, 그것이 4편에서 본 선점으로 이어집니다.

max_num_batched_tokens와 max_num_seqs

이 둘은 이름 때문에 길이 한도처럼 보이지만 성격이 완전히 다릅니다. 요청 하나에 대한 제한이 아니라 한 스텝에 엔진이 처리할 작업량에 대한 제한입니다.

max_num_batched_tokens는 이번 스텝에 배치 전체가 처리할 토큰 수의 상한입니다. 요청 하나가 이 값을 넘는다고 거부되지 않습니다. 청크드 프리필이 켜져 있으면 그냥 여러 스텝에 나뉘어 처리됩니다.

max_num_seqs는 한 스텝에 동시에 담을 시퀀스 수의 상한입니다. 이걸 넘으면 대기할 뿐 실패하지 않습니다.

main 브랜치 SchedulerConfig에는 각각의 클래스 기본값이 2048과 128로 선언되어 있습니다. 다만 실제 적용값은 사용 맥락과 환경에 따라 조정될 수 있으니 기동 로그를 확인하세요.

이 둘이 길이 문제와 만나는 지점이 딱 하나 있습니다. 공식 최적화 문서는 청크드 프리필을 껐을 경우 max_num_batched_tokensmax_model_len보다 커야 한다고 명시합니다. 나눠 처리할 수 없는데 최대 길이 프롬프트를 한 스텝에 담을 수 없다면 방법이 없기 때문입니다.

시나리오 1 — 8k 모델에 6k 입력과 max_tokens 4000

가장 흔한 경우부터 봅니다. 컨텍스트 8192인 모델을 올렸고, 사용자가 6000토큰짜리 문서를 붙여 넣으면서 max_tokens를 4000으로 줬습니다.

max_model_len = 8192  (입력 + 출력이 함께 쓰는 예산)

입력  6000 ██████████████████████████████
출력  4000 ████████████████████
합계 10000 ──────────────────────────────────────────  8192 초과

결과: 생성이 시작되기도 전에 요청이 거부됩니다.

여기서 초심자가 가장 많이 하는 예상은 "입력 6000이 8192보다 작으니 통과하고, 답변이 2192토큰쯤에서 잘리겠지"입니다. 그렇지 않습니다. vLLM은 요청을 받는 시점에 입력과 max_tokens를 더해서 검사하고, 넘으면 거부합니다.

실제로 나오는 메시지는 이런 형태입니다. 공개된 vLLM 이슈에 보고된 원문을 그대로 옮깁니다.

This model's maximum context length is 16384 tokens. However, you requested
122946 tokens (112946 in the messages, 10000 in the completion).
Please reduce the length of the messages or completion.

메시지를 뜯어보면 구조가 그대로 보입니다. 괄호 안이 입력과 출력으로 나뉘어 있고, 둘의 합이 앞의 한도와 비교됩니다. 이 한 줄이 "입력과 출력은 같은 예산을 나눠 쓴다"는 사실의 가장 확실한 증거입니다.

그래서 해법도 셋 중 하나입니다. 입력을 줄이거나, max_tokens를 줄이거나, max_model_len을 늘리는 것입니다. 앞의 둘은 요청 쪽 수정이고 마지막은 서버 재기동이 필요합니다. 참고로 자동으로 잘라 주지 않고 거부하는 이 동작에 대해서는 vLLM 저장소에 개선 요청 이슈가 올라와 있습니다. 즉 버전에 따라 달라질 수 있는 영역이니 사용 중인 버전에서 직접 확인하세요.

실전 팁 하나. 애플리케이션에서 max_tokens를 상수로 박아 두면 긴 입력이 들어올 때마다 이 에러를 맞습니다. 입력 토큰 수를 센 뒤 남은 여유에서 안전 여백을 뺀 값으로 계산해 넣는 편이 안정적입니다.

시나리오 2 — 128k 모델인데 왜 8192에서 막히는가

모델 카드에는 128k라고 적혀 있는데 서버가 훨씬 작은 값에서 요청을 거부합니다. 확인 순서는 이렇습니다.

첫째, 기동 명령에 --max-model-len을 직접 준 적이 있는지 봅니다. 줬다면 그 값이 답입니다. 모델 능력과 무관하게 엔진이 정한 한도가 우선합니다.

둘째, 안 줬다면 엔진이 모델 설정에서 유도한 값을 봅니다. 공식 문서가 밝힌 대로 지정하지 않으면 모델 설정에서 자동 유도되는데, 이 값이 모델 카드의 홍보 문구와 다를 수 있습니다. 확장 기법으로 긴 컨텍스트를 지원하는 모델은 설정 파일에 더 작은 기본 창이 적혀 있기도 합니다.

셋째, 에러 메시지가 말하는 숫자를 믿습니다. 앞의 메시지에서 "maximum context length is" 뒤에 나오는 값이 지금 이 엔진의 실효 한도입니다. 모델 카드보다 이 숫자가 사실입니다.

넷째, 그래도 128k가 필요하다면 KV 캐시를 감당할 수 있는지부터 계산합니다. 그리고 이것이 세 번째 시나리오로 이어집니다.

시나리오 3 — 요청 이전에 기동에서 막히는 경우

--max-model-len을 크게 줬더니 서버가 아예 안 뜨는 경우입니다. vLLM 이슈에 보고된 원문 형태는 이렇습니다.

ValueError: The model's max seq len (4096) is larger than the maximum number
of tokens that can be stored in KV cache (3664). Try increasing
gpu_memory_utilization or decreasing max_model_len when initializing the engine.

이 메시지가 이 글에서 가장 중요할지도 모릅니다. 왜냐하면 여기에 진짜 구조가 드러나기 때문입니다. 엔진은 기동할 때 가중치를 올리고 남은 메모리로 KV 캐시를 잡습니다. 그리고 그 캐시에 토큰 몇 개를 담을 수 있는지 계산합니다. 만약 그 수가 max_model_len보다 작으면, 최대 길이 요청 하나조차 완주시킬 수 없다는 뜻이라 기동을 중단합니다.

즉 길이 한도의 최종 결정권자는 모델도 설정도 아니고 메모리입니다.

진짜 한계를 정하는 것은 KV 캐시 메모리

앞의 시나리오를 일반화하면 이렇게 됩니다.

GPU 전체 메모리
  └─ gpu_memory_utilization 비율만큼을 vLLM이 사용
       ├─ 모델 가중치            (모델 크기와 양자화가 결정)
       ├─ 활성화 및 각종 오버헤드
       └─ 남은 전부 = KV 캐시    ← 여기가 길이와 동시성을 함께 먹는다

KV 캐시는 두 가지 요구를 동시에 받습니다. 하나는 길이입니다. 요청 하나가 길수록 블록을 많이 씁니다. 다른 하나는 동시성입니다. 요청이 많을수록 블록을 많이 씁니다. 그래서 max_model_len을 두 배로 올리면 같은 메모리로 살릴 수 있는 동시 요청 수가 대략 절반이 됩니다.

이 관계가 실무에서 이렇게 나타납니다. 128k를 열어 두면 실제로 128k를 쓰는 사용자가 없어도 동시 처리 능력이 줄어듭니다. 엔진이 최악의 경우를 감당할 수 있어야 기동하기 때문입니다. 그래서 대부분의 프로덕션 배포는 모델이 지원하는 최대치가 아니라 실제 워크로드의 상위 백분위에 맞춰 max_model_len을 잡습니다. 앞에서 이 값을 낮추는 것이 실수가 아니라고 한 이유입니다.

에러 메시지로 원인 찾기

마지막으로 증상에서 거꾸로 찾아가는 표입니다.

증상가장 유력한 원인먼저 확인할 것
요청이 400 계열로 거부되고 메시지에 messages와 completion 숫자가 보임입력과 max_tokens의 합이 max_model_len 초과요청의 max_tokens 계산 방식
서버 기동 중 KV 캐시에 담을 수 있는 토큰 수가 부족하다는 에러max_model_len이 가용 메모리에 비해 큼--gpu-memory-utilization--max-model-len
답변이 문장 중간에서 갑자기 끊김요청의 생성 상한에 도달응답의 종료 사유 필드
모델 카드보다 작은 값에서 막힘엔진이 유도했거나 지정된 max_model_len기동 로그의 실제 적용값
동시 요청이 늘면 급격히 느려짐길이 한도가 아니라 KV 캐시 부족과 선점선점 경고 로그 (4편 참고)

정리하면 순서는 하나입니다. 먼저 에러 메시지가 요청 단계에서 났는지 기동 단계에서 났는지 구분하고, 요청 단계면 입력과 생성 상한의 합을 보고, 기동 단계면 메모리와 max_model_len의 관계를 봅니다. 이 두 갈래만 구분해도 길이 문제의 대부분은 십 분 안에 끝납니다.

직접 해보기

참고 자료