TT Lab
블로그

한국어

시작하기
블로그

블로그

병렬화, 멀티모달, 풀링: GPU 한 장으로 잴 수 있는 것과 없는 것

상태: 초안 (2026-10-27), 풀링·멀티모달 실측, 병렬화 미실측. 풀링 측정은 8GB급 노트북 GPU(Ada 세대) 한 장, vLLM 0.31.0 으로 2026-10-27 에 했습니다. 병렬화와 멀티모달은 미실측이며, 공식 문서(0.32.0)와 논문에서 확인한 내용만 적었습니다. LoRA 다중 서빙의 실측은 「토스 ML/AI Infra 준비」 15편에 있습니다. 2026-10-27 추가. 승인을 받아 vLLM 0.32.0 이미지와 Qwen3-VL-2B-Instruct(Apache-2.0, safetensors 4.26GB)를 받아 2-1절의 멀티모달 실측을 더했습니다. 처음 판에서 「미실측」이던 내용입니다. 작성 규칙: 실측 데이터가 기본이고, 못 잰 것은 「미실측」, 확인하지 못한 것은 「미확인」으로 표시합니다.

목차

1. 병렬화 네 가지: 무엇을 나누는가

방식 옵션 나누는 것 통신 쓰는 때
텐서 병렬(TP) --tensor-parallel-size 한 층의 행렬을 GPU 마다 조각으로 층마다 all-reduce 모델이 GPU 한 장에 안 들어갈 때, 한 서버 안에서
파이프라인 병렬(PP) --pipeline-parallel-size 층을 앞뒤로 나눠 GPU 마다 몇 층씩 단계 사이 활성값만 서버 여러 대에 걸칠 때, NVLink 가 없을 때
데이터 병렬(DP) --data-parallel-size 나누지 않고 모델을 통째로 여러 벌 거의 없음(MoE 는 있음) 처리량을 늘릴 때
전문가 병렬(EP) --enable-expert-parallel MoE 의 전문가들을 GPU 마다 토큰을 전문가 쪽으로 all-to-all 큰 MoE 모델

모델이 들어가는지 볼 때는 기동 로그의 두 줄을 봅니다. GPU KV cache size: N tokens 와 Maximum concurrency for M tokens per request: Kx 입니다. 이 시리즈의 실험 서버는 「40,208토큰, 4,096토큰 요청 기준 9.82배」였습니다. 이 동시성이 목표보다 낮으면 GPU 를 늘리라고 문서는 안내합니다.

GPU 가 한 장이라 네 방식 모두 미실측입니다. 같은 계열 GPU 두 대를 1GbE 로 묶어 KV 를 옮겨 본 실측(분리 서빙이 20배 넘게 느렸음)이 「토스 ML/AI Infra 준비」 24편에 있어, 느린 연결에서 GPU 를 잇는 방식이 어떤 값을 치르는지는 그 글로 대신합니다.

2. 멀티모달 입력

vLLM 은 이미지·영상·오디오를 받는 모델을 같은 OpenAI 호환 API 로 서빙합니다. 채팅 메시지의 content 에 image_url, video_url, input_audio 조각을 넣으면, API 서버의 처리기가 미디어를 내려받고 전처리해 모델의 인코더로 보냅니다. 이미지 하나는 프롬프트 안에서 자리표시 토큰 여러 개로 바뀌고, 접두사 캐시는 이 자리표시 블록의 해시에 이미지의 해시를 섞어 같은 이미지끼리만 캐시를 나눠 씁니다(2편의 접두사 캐시 설계 문서).

운영에서 먼저 챙길 것은 성능보다 이쪽입니다.

처음 판에서는 시각 언어 모델을 새로 받아야 해서 미실측이었고, 승인 뒤 아래 2-1절에서 쟀습니다.

2-1. 2026-10-27 실측: 8GB 에 2B 시각 언어 모델 올리기

먼저, 뜨게 만들기

설정 KV 에 남은 메모리 KV 자리 결과
기본(--gpu-memory-utilization 0.85) -0.07GiB 기동 실패 「No available memory for the cache blocks」
--limit-mm-per-prompt '{"image":1,"video":0}' 만 더함 -0.07GiB 같은 실패
메모리 비율 0.92 0.47GiB 4,352토큰(4,096토큰 요청 1.06개) 뜸
0.92 + 크기 힌트 {"image":{"count":1,"width":896,"height":896},"video":0} 2.13GiB 19,904토큰(4.86개) 뜸

vLLM 은 기동할 때 「모델이 받을 수 있는 가장 큰 이미지」로 인코더를 한 번 돌려 메모리를 잽니다. 로그는 인코더 캐시 예산을 16,384토큰으로 잡았다고 적었습니다. 가중치 4GB 에 그 예산까지 얹으니 8GB 에서는 KV 자리가 남지 않았습니다. 이미지 개수만 줄여서는 소용이 없었고, 실제로 받을 이미지 크기를 알려 주자 KV 가 4.5배가 되었습니다. 작은 GPU 에서 시각 언어 모델을 띄울 때 가장 먼저 만질 손잡이입니다.

결과(크기 힌트 서버)

항목 448×448 896×896
프롬프트 토큰(이미지 + 질문) 220 808
새 이미지의 TTFT 중앙 67.9ms 238.0ms
같은 이미지, 같은 질문 다시 31.8ms(캐시 208토큰) 32.7ms(캐시 800토큰)
같은 이미지, 다른 질문 32.1ms(캐시 192토큰) 33.4ms(캐시 784토큰)

SSRF: 서버가 대신 내려받는가

요청의 이미지 주소 기본 서버 --allowed-media-domains example.com
http://127.0.0.1:8000/health(서버 자신) 400 「Failed to load image: cannot identify image file」(5.5ms) 400 「The URL must be from one of the allowed domains」(2.6ms)
http://127.0.0.1:8000/metrics 400, 같은 메시지 400, 같은 거절
file:///etc/passwd 500 「Cannot load local files without --allowed-local-media-path」 같음

기본 서버의 메시지는 「이미지가 아니다」입니다. 곧 서버가 그 내부 주소에 실제로 요청을 보내 응답을 받아 왔다는 뜻입니다. 응답 내용이 돌아오지는 않았지만, 요청자는 이 서버를 통해 내부망을 두드리고 응답 시간과 오류로 무엇이 열려 있는지 알아낼 수 있습니다. 도메인을 정하자 받기 전에 거절했습니다. 로컬 파일은 기본으로 막혀 있었습니다. 이미지 URL 을 받는 서비스라면 --allowed-media-domains 는 선택이 아니라 필수입니다.

3. 풀링: 임베딩과 재순위도 같은 엔진으로

생성하지 않고 마지막 은닉 상태를 뽑아 쓰는 모델을 vLLM 은 풀링 모델이라 부릅니다. --runner pooling 으로 띄우면 생성 대신 /v1/embeddings, /score, /rerank, /classify, /pooling 을 제공합니다. 인과 모델은 마지막 토큰(LAST), BERT 계열은 첫 토큰(CLS)을 기본으로 씁니다. 공식 문서는 「풀링 모델 지원은 편의를 위한 것이고 다른 방식보다 빠르다는 보장은 없다」고 먼저 적어 둡니다.

방법

결과

작업 한 요청 지연 중앙(5회) 처리량 비고
임베딩 문장 64개 × 약 127토큰 371.8ms 초당 172개(약 21,900토큰) 차원 1,024
재순위 질문 1 + 문서 32개 33.9ms 초당 944개 맞는 문서가 0.993 으로 1위

4. LoRA 다중 서빙

기본 모델 하나에 LoRA 어댑터 여러 개를 얹어 요청마다 다른 어댑터를 쓰는 기능입니다(--enable-lora --max-loras --max-lora-rank). 같은 8GB급 GPU 에서 1.7B 모델로 잰 값은 LoRA 어댑터 서빙에 있습니다. 어댑터를 쓰면 8.5~17.9% 느렸고, 어댑터 수가 슬롯 수를 넘으면 처리량이 크게 떨어졌습니다. 이번 판에서 덧붙일 것은 두 가지입니다.

5. 확인하지 못한 것

참고

로그인하면 좋아요를 누를 수 있습니다

댓글

아직 댓글이 없습니다.

로그인하면 댓글을 쓸 수 있습니다