사내 문서나 고객 스크린샷을 외부 API 로 보낼 수 없어서 오픈 비전 LLM 을 직접 띄우기로 했다면, 모델 다음으로 정할 값은 이미지 한 장을 몇 토큰까지 허용할지예요. 결론부터 말하면 Qwen2-VL 계열은 max_pixels 를 1280×28×28(약 1,280토큰)로 묶는 것을 기본값으로 두고, 분류·태깅만 하면 256토큰, 아주 작은 글씨가 핵심인 화면만 원본 해상도로 열어 두는 게 무난해요. 기본값(상한 16,384토큰)을 그대로 두면 1920×1080 한 장이 1,280 상한일 때보다 GPU 연산을 2.5배 써요.
API 는 이미지 토큰 수만큼 요금을 받지만, 직접 서빙하면 그 숫자가 세 군데로 나눠 청구돼요. 비전 인코더 연산(첫 토큰 지연), LLM 프리필 연산(GPU 시간), KV 캐시(동시 요청 수)예요. 이 글은 공개된 모델 설정 파일과 전처리 코드로 세 가지를 직접 계산하고, Lambda GPU 가격표의 시간 단가로 비용까지 바꿔 봐요.
직접 서빙하면 이미지 비용이 세 군데로 나뉘어요
- 비전 인코더 연산: 이미지를 14px 패치로 잘라 트랜스포머에 넣어요. Qwen2-VL 은 패치 4개를 나중에 토큰 1개로 합치니, 인코더가 실제로 처리하는 길이는 LLM 에 들어가는 이미지 토큰의 4배예요. Qwen2-VL 논문의 비전 인코더는 창 없이 모든 패치끼리 어텐션해서 이 부분이 길이의 제곱으로 커져요.
- LLM 프리필 연산: 이미지 토큰과 질문을 한꺼번에 읽는 단계예요. 대부분 "파라미터 수 × 2 × 토큰 수"에 비례하고, 이게 GPU 시간과 요금이 돼요.
- KV 캐시: 프리필이 끝나도 답을 다 만들 때까지 이미지 토큰의 키·값을 GPU 메모리에 들고 있어요. 한 장이 차지하는 크기가 곧 동시에 받을 수 있는 요청 수를 정해요.
셋은 따로 움직여요. 가로세로를 두 배로 하면 토큰과 프리필은 4배, 인코더 어텐션은 16배가 되고, KV 캐시는 같은 토큰 수라도 모델 구조에 따라 9배까지 벌어져요.
모델·설정별 이미지 토큰 수 — 전처리 코드로 직접 계산
토큰 수는 전처리 규칙이 정해요. transformers 의 Qwen2-VL 이미지 프로세서는 가로세로를 28의 배수로 반올림하고, 넓이가 max_pixels 를 넘으면 비율을 지키며 줄여요. Qwen2-VL-7B-Instruct 모델 카드는 기본 범위를 이미지당 4~16,384토큰으로 적고, 속도와 메모리를 위해 256~1,280처럼 좁히라고 안내해요. 배포된 preprocessor_config.json 의 max_pixels 도 12,845,056(=16,384×28×28)이에요.
LLaVA-NeXT 는 다른 길을 써요. LLaVA-NeXT 발표 글대로 336px 타일을 2~4개 붙인 격자(672×672, 336×1008 등) 중 이미지에 가장 맞는 것을 골라 타일마다 576토큰을 만들고, 전체를 줄인 기본 이미지 576토큰을 더해요. 정확한 수는 transformers 의 LLaVA-NeXT 프로세서의 계산(여백 제거, 줄바꿈 토큰 포함)을 그대로 옮겨 구했어요. LLaVA-1.5 는 짧은 변을 336에 맞추고 가운데를 잘라 항상 576토큰이에요.
| 이미지 | LLaVA-1.5 | LLaVA-NeXT | Qwen2-VL 256 상한 | 640 상한 | 1,280 상한 | 기본(16,384 상한) |
|---|---|---|---|---|---|---|
| 1920×1080 화면 캡처 | 576 | 1,948 | 254 | 596 | 1,224 | 2,693 |
| 1080×2340 폰 캡처 | 576 | 1,876 | 232 | 631 | 1,250 | 3,278 |
| 800×1100 영수증 사진 | 576 | 2,256 | 236 | 611 | 1,133 | 1,133 |
| 3840×2160 4K 캡처 | 576 | 1,948 | 254 | 596 | 1,224 | 10,551 |
Qwen2-VL 숫자에는 이미지 앞뒤 시작·끝 토큰 2개를 넣었어요. 영수증처럼 원래 작은 이미지는 1,280 상한 안에 들어와서 기본값과 같아요. 상한은 큰 이미지에만 걸리는 장치라, 들어오는 이미지가 대부분 작다면 상한을 낮춰도 비용이 거의 안 줄어요.
프리필 연산과 1,000장당 GPU 비용
토큰 수를 연산량으로 바꿨어요. 각 모델의 공개 config.json 에서 층 수·폭·KV 헤드 수를 읽어 선형층은 "2 × 파라미터 × 길이", 어텐션은 "4 × 층 × 길이² × 폭"(LLM 은 인과 마스크라 절반)으로 셌어요. 질문 텍스트는 100토큰으로 두었고, 답을 만드는 디코딩은 설정과 상관없이 비슷해서 빼고 이미지를 읽는 데 드는 몫만 봤어요.
기본 설정에서는 인코더가 전체의 46%, 256 상한에서는 23%예요
표로 보기
| 구분 | 비전 인코더 | LLM 프리필 |
|---|---|---|
| 256 상한 | 1.4 | 4.7 |
| 640 상한 | 3.9 | 9.2 |
| 1280 상한 | 10.1 | 17.6 |
| 기본 | 32.5 | 38 |
상한을 1,280에서 기본값으로 풀면 토큰은 2.2배인데 인코더 연산은 3.2배가 돼요. 4K 캡처를 기본값으로 넣으면 패치가 42,196개라 인코더 어텐션만 292 TFLOP, 전체 507 TFLOP 이에요. 인코더 어텐션의 점수 행렬을 통째로 만드는 구현이라면 한 층에만 57GB(헤드 16개, 16비트)가 필요하니, FlashAttention 류 구현이 켜져 있는지부터 확인해요. 구현별 메모리 차이는 어텐션 구현 고르기, SDPA·FlashAttention 메모리와 GPU 비용에 정리돼 있어요.
Qwen2.5-VL-7B 의 config.json을 보면 인코더에 window_size: 112 와 전체 어텐션 층 4개(fullatt_block_indexes: [7, 15, 23, 31])가 들어가 있어요. 32층 중 28층을 창 안에서만 어텐션해서 고해상도 인코더 비용의 제곱 항을 줄인 설계예요. 큰 이미지를 자주 받는다면 후속 모델로 옮기는 것도 선택지예요.
연산량을 돈으로 바꾸려면 GPU 성능과 단가가 필요해요. BF16 밀집 연산 최대치는 NVIDIA H100 아키텍처 백서의 H100 SXM5 989.4 TFLOPS, NVIDIA A10 데이터시트의 125 TFLOPS 를 썼어요. 단가는 Lambda 가격표의 1장 인스턴스 가격(H100 SXM 4.89달러, A10 1.29달러, 2026-11-16 적용분, 2026-11-07 확인, 세금 별도)이에요. 실제로 최대치를 다 쓰지는 못하니 활용률 40%는 가정이고, 숫자는 GPU 가 쉬지 않고 이미지만 처리할 때의 하한이에요.
| 설정 (1920×1080) | 연산량 | H100 한 장 | A10 한 장 | H100 100만 장 | A10 100만 장 |
|---|---|---|---|---|---|
| Qwen2-VL 256 상한 | 6.1 TFLOP | 15ms | 122ms | 21달러 | 44달러 |
| LLaVA-1.5 | 9.3 TFLOP | 23ms | 186ms | 32달러 | 67달러 |
| Qwen2-VL 640 상한 | 13.2 TFLOP | 33ms | 263ms | 45달러 | 94달러 |
| Qwen2-VL 1,280 상한 | 27.8 TFLOP | 70ms | 556ms | 95달러 | 199달러 |
| LLaVA-NeXT | 31.7 TFLOP | 80ms | 634ms | 109달러 | 227달러 |
| Qwen2-VL 기본 | 70.8 TFLOP | 179ms | 1.42초 | 243달러 | 507달러 |
두 가지가 보여요. 첫째, 같은 연산이면 A10 이 H100 보다 2배 넘게 비싸요. 시간당 단가는 A10 이 싸지만 달러당 연산량이 H100 은 202 TFLOPS, A10 은 97 TFLOPS 거든요. 싼 GPU 가 이득인 건 요청이 적어 GPU 가 대부분 놀 때예요. 둘째, 한 장 시간은 첫 토큰 지연의 바닥값이에요. A10 에서 기본 설정이면 이미지를 읽는 데만 1.4초가 걸리니, 채팅처럼 바로 답해야 하는 기능에는 상한을 거는 게 사실상 필수예요. 외부 API 요금과 비교하고 싶다면 같은 이미지의 API 토큰 수와 단가를 정리한 멀티모달 LLM 이미지 토큰 비용 비교를 함께 보면 돼요.
KV 캐시와 동시 요청 수 — 토큰 수보다 모델 구조
KV 캐시 한 토큰 크기는 "2(키·값) × 층 × KV 헤드 × 헤드 차원 × 2바이트"예요. 공개 설정으로 계산하면 LLaVA-1.5-7B(Vicuna-7B, KV 헤드 32개) 512KiB, LLaVA-NeXT-Mistral-7B(KV 헤드 8개) 128KiB, Qwen2-VL-7B(KV 헤드 4개) 56KiB 예요. KV 헤드를 줄이는 GQA 가 있느냐 없느냐로 9배가 차이 나요.
그래서 이미지 토큰이 가장 적은 LLaVA-1.5 가 메모리로는 가장 무거워요. 아래는 H100 80GB 에서 가중치·활성값을 빼고 KV 캐시에 45GiB 를 쓸 수 있다고 가정하고, 요청마다 FHD 이미지 1장 + 텍스트 400토큰(질문 100, 답 300)을 담을 때 동시에 들고 있을 수 있는 요청 수예요.
이 표는 메모리 상한이고, 실제 처리량은 앞의 연산 시간과 둘 중 작은 쪽에서 멈춰요. 블록 단위 할당과 동시 요청 수를 GPU 대수로 바꾸는 방법은 vLLM KV 캐시 용량 계산과 같아요. MiB·GiB 단위가 헷갈리면 데이터 용량 변환기로 바로 확인할 수 있어요.
품질은 무엇과 바꾸나 — 남는 해상도와 공개 점수
토큰을 줄이면 모델이 보는 이미지가 작아져요. 1920×1080 기준으로 모델이 실제로 보는 크기와 원본 대비 축소 배율이에요.
| 설정 | 모델이 보는 크기 | 축소 배율 | 원본 16px 글자 |
|---|---|---|---|
| Qwen2-VL 기본 | 1932×1092 | 1.01 | 약 16px |
| Qwen2-VL 1,280 상한 | 1316×728 | 0.69 | 약 11px |
| Qwen2-VL 640 상한 | 924×504 | 0.48 | 약 8px |
| LLaVA-NeXT | 672×378(+전체 축소본) | 0.35 | 약 6px |
| LLaVA-1.5 | 336×336, 가운데만 | 0.31 | 약 5px, 좌우 잘림 |
| Qwen2-VL 256 상한 | 588×336 | 0.31 | 약 5px |
공개 점수도 같은 방향이에요. 글자 읽기 벤치마크 TextVQA 에서 LLaVA-NeXT 발표 글은 LLaVA-1.5-13B 61.3, LLaVA-NeXT-Mistral-7B 65.7 을 적었고, Qwen2-VL-7B 모델 카드는 84.3(DocVQA 94.5)을 적었어요. 다만 각자 발표한 점수라 평가 조건이 완전히 같지 않고, 해상도 상한을 낮춘 설정의 점수는 공개되지 않았어요. 상한별 품질은 내 이미지로 직접 재야 하는 값이에요.
상황별 추천 — 상한을 어디에 둘까
- 고객 문의 스크린샷, 사내 화면 Q&A: Qwen2-VL 계열에 1,280 상한이 출발점이에요. 정답률이 같으면 640으로 내려 연산을 절반으로 줄여요.
- 사진 분류·태깅·검수 대량 처리: 256 상한이면 FHD 100만 장이 H100 기준 21달러(가정)예요. 밤에 몰아서 돌리면 활용률을 높이기도 쉬워요.
- 문서 본문·영수증 숫자 추출: 배치나 색이 필요 없다면 OCR 엔진으로 글자를 뽑고 텍스트 LLM 에 넘기는 편이 단순해요. 글자 400토큰과 질문 100토큰을 7B 모델로 읽으면 약 6.5 TFLOP 로 256 상한 이미지와 비슷하지만, OCR 이 놓친 글자는 복구할 수 없어요.
- 각주·도면·빽빽한 표, 화면 좌표 클릭: 원본 해상도가 필요해요. 이미지 전체를 키우지 말고 필요한 영역을 잘라 원본 배율로 넣어요. 4K 전체를 기본값으로 넣으면 한 장 507 TFLOP 이에요.
- 이미 LLaVA-1.5 를 쓰고 있다: KV 캐시가 무겁고 가장자리를 잘라 내요. 동시 요청이 늘거나 긴 화면을 받기 시작하면 동적 해상도 모델로 옮길 때예요.
흔한 실수 — 비용과 지연이 예상보다 커지는 이유
상한을 안 걸고 배포한다. Qwen2-VL 기본 상한은 16,384토큰이라, 누군가 4K 캡처나 긴 웹페이지 전체 캡처를 올리면 한 요청이 수만 패치를 인코더에 밀어 넣어요. 그 한 장 때문에 같은 배치의 다른 요청 첫 토큰까지 늦어져요.
서빙 엔진과 테스트 코드의 상한이 다르다. 노트북에서는 프로세서에 max_pixels 를 넘겨 테스트하고, 서버에서는 엔진 설정을 빠뜨리는 경우가 많아요. vLLM 메모리 절약 문서는 mm_processor_kwargs 로 max_pixels 를 주고 limit_mm_per_prompt 로 요청당 이미지 수를 묶으면 미리 잡는 메모리가 줄어든다고 안내해요. 두 곳의 값을 한 설정 파일에서 읽게 해요.
토큰 수만 보고 메모리를 어림한다. 앞에서 본 것처럼 576토큰짜리 LLaVA-1.5 가 2,693토큰짜리 Qwen2-VL 기본 설정보다 KV 캐시를 2배 가까이 써요. 모델을 바꿀 때는 config.json 의 num_key_value_heads 부터 봐요.
작은 이미지에 상한 효과를 기대한다. 800×1100 영수증은 1,280 상한 아래라 상한을 바꿔도 1,133토큰 그대로예요. 들어오는 이미지 크기 분포를 먼저 뽑아야 상한 조정의 효과를 알 수 있어요.
min_pixels 를 크게 잡는다. 모델 카드 예시처럼 min_pixels=256×28×28 이면 아이콘만 한 이미지도 256토큰 크기로 키워요.
도입 체크리스트
- 서비스에 실제로 들어오는 이미지의 가로·세로를 1,000장 정도 모아 분포를 봐요. 상위 10%가 비용을 정해요.
- 아래 함수로 상한 후보(256·640·1,280·기본)별 토큰 수 분포를 계산해요.
- 이미지 50~100장과 정답을 만들어 상한별 정답률을 재요. 허용 범위 안의 가장 작은 상한을 골라요.
- 서빙 엔진에 같은 값을 넣고, 요청당 이미지 수 상한도 걸어요.
- 엔진 로그나 응답 usage 의 입력 토큰 수가 계산값과 맞는지 확인해요.
- 첫 토큰 지연과 동시 요청 수를 부하 테스트로 재서 앞의 계산(하한)과 비교해요.
import math
def qwen2vl_image_tokens(w, h, max_tokens=16384, min_pixels=56 * 56, f=28):
"""transformers smart_resize 와 같은 규칙. 시작·끝 토큰 2개 포함."""
max_pixels = max_tokens * f * f
hb, wb = round(h / f) * f, round(w / f) * f
if hb * wb > max_pixels:
b = math.sqrt(h * w / max_pixels)
hb = max(f, math.floor(h / b / f) * f)
wb = max(f, math.floor(w / b / f) * f)
elif hb * wb < min_pixels:
b = math.sqrt(min_pixels / (h * w))
hb, wb = math.ceil(h * b / f) * f, math.ceil(w * b / f) * f
return (hb // f) * (wb // f) + 2, (wb, hb)
for w, h in [(1920, 1080), (1080, 2340), (800, 1100)]:
row = [qwen2vl_image_tokens(w, h, cap)[0] for cap in (256, 640, 1280, 16384)]
print(f"{w}x{h}", row)
1920x1080 [254, 596, 1224, 2693]
1080x2340 [232, 631, 1250, 3278]
800x1100 [236, 611, 1133, 1133]
vLLM 에서는 엔진을 만들 때 같은 값을 넘겨요.
from vllm import LLM
llm = LLM(
model="Qwen/Qwen2-VL-7B-Instruct",
mm_processor_kwargs={"max_pixels": 1280 * 28 * 28}, # 이미지당 약 1,280토큰 상한
limit_mm_per_prompt={"image": 2, "video": 0}, # 요청당 이미지 2장, 영상 받지 않음
)
OpenAI 호환 서버로 띄워 이미지를 data URL 로 보내면 본문이 원본 바이트보다 약 33% 커져요. 전송 크기는 Base64 인코더에 넣어 보면 바로 나와요.
자주 묻는 질문
max_pixels 는 어느 값부터 시작하면 되나요?
글씨가 들어간 화면을 다룬다면 1280×28×28(약 1,280토큰)이 무난한 출발점이에요. 모델 카드도 256~1,280 범위를 예로 들어요. 내 이미지로 정답률을 재서 같으면 640으로 내리고, 떨어지면 해당 영역만 크롭해 원본 배율로 넣는 쪽을 먼저 시도해요.
해상도를 두 배로 올리면 비용도 두 배인가요?
아니에요. 가로세로를 두 배로 하면 토큰과 LLM 프리필은 약 4배, Qwen2-VL 비전 인코더의 어텐션은 패치 수의 제곱이라 약 16배가 돼요. FHD 기준으로 상한 1,280에서 기본값으로 풀면 토큰은 2.2배인데 전체 연산은 2.5배가 늘어요.
LLaVA-1.5 가 토큰이 적으니 가장 싼가요?
연산은 싼 편이지만 메모리는 가장 비싸요. KV 헤드가 32개라 토큰 하나가 512KiB 라서, 576토큰 이미지 한 장이 288MiB 를 차지해요. 또 가운데를 잘라 넣어서 가로로 긴 화면은 좌우 44%를 보지 못해요.
A10 같은 싼 GPU 가 더 경제적이지 않나요?
요청이 적어 GPU 가 대부분 쉬는 서비스라면 시간 단가가 싼 쪽이 유리해요. 하지만 계속 바쁘게 돌린다면 이 글의 계산에서 같은 이미지를 처리하는 비용이 A10 이 H100 의 2배 남짓이었어요. A10 은 24GB 라 7B 모델 가중치를 올리고 나면 KV 캐시 자리도 넉넉하지 않아요.
이미지 대신 OCR 텍스트를 넣으면 무조건 싼가요?
LLM 쪽만 보면 비슷하거나 싸요. 글자 400토큰이면 256 상한 이미지와 연산량이 비슷해요. 대신 OCR 엔진을 따로 운영해야 하고, 표의 배치·색·아이콘처럼 글자가 아닌 정보와 OCR 이 놓친 글자는 다음 단계에서 다시 볼 수 없어요.
정리
- 직접 서빙하면 이미지 비용은 비전 인코더 연산, LLM 프리필 연산, KV 캐시 세 군데로 나뉘어요. 상한 하나(
max_pixels)가 셋을 함께 움직여요. - Qwen2-VL 계열은 1,280토큰 상한이 기본값, 분류는 256, 작은 글씨는 원본 배율 크롭이에요. 기본값 그대로면 FHD 한 장 연산이 2.5배, 4K 는 18배가 돼요.
- 메모리는 토큰 수보다 KV 헤드 수가 크게 좌우하고, GPU 는 시간 단가보다 달러당 연산량으로 비교해요. 단가는 2026-11-07 확인 기준이니 도입 전에 가격표를 다시 봐요.
더 깊이 읽기
이 글은 실무에서 판단할 때 필요한 것 위주로 정리했어요. 같은 주제의 내부 동작 원리는 devboi 기술 블로그의 멀티모달 LLM 원리 — 이미지가 토큰이 되는 과정(ViT·LLaVA)에서 도식과 코드로 단계별로 풀었어요.