멀티모달 데이터 처리

멀티모달 데이터 처리

vLLM으로 이미지 같은 멀티모달 입력을 다루다 보면, Qwen2-VL처럼 멀티모달 처리 자체가 느려 문제가 되는 경우를 마주치게 돼요. vLLM은 chunked prefill이나 prefix caching 같은 최적화를 여러 개 활성화하기 위해, HF processor의 출력을 바탕으로 플레이스홀더 피처 토큰(예: <image>)과 멀티모달 입력(예: 원본 입력 이미지) 사이의 대응 관계를 만들어 주는 [BaseMultiModalProcessor][vllm.multimodal.processing.BaseMultiModalProcessor]를 사용해요.

vLLM의 렌더링 파이프라인(자세한 내용은 [BaseRenderer][vllm.renderers.base.BaseRenderer] 참고)에서 토크나이제이션은 멀티모달 처리보다 앞선 별도 스텝으로 수행돼요. 그래서 BaseMultiModalProcessor는 원래 텍스트를 직접 볼 수 없는 상태에서 HF processor를 호출한 결과를 처음부터 다시 만들어야 해요. 이를 **Dummy Input Text(더미 입력 텍스트)**와 **Prompt Update Detection(프롬프트 업데이트 감지)**로 해결하죠.

출처: vLLM Multi-Modal Data Processing

Dummy Input Text

Transformers 5.10부터 ProcessorMixin은 멀티모달 입력을 단독으로 전달할 수 있게 됐어요. 하지만 ChameleonProcessor 같은 일부 서브클래스나 오래된 out-of-tree 구현은 여전히 대응하는 플레이스홀더 토큰이 있는 텍스트가 함께 존재한다고 가정하는 자체 __call__ 메서드를 정의하고 있을 수 있어요. 이런 processor에는 전달할 원래 텍스트가 없으니 문제가 되죠.

이를 해결하기 위해 각 모델은 멀티모달 입력 개수를 기반으로 더미 텍스트를 어떻게 생성할지 [get_dummy_text][vllm.multimodal.processing.BaseDummyInputsBuilder.get_dummy_text]로 정의해요. [_get_hf_processor_text][vllm.multimodal.processing.BaseMultiModalProcessor._get_hf_processor_text]의 오버라이드가 그 더미 텍스트를 반환하면, [_apply_hf_processor_main][vllm.multimodal.processing.BaseMultiModalProcessor._apply_hf_processor_main]이 멀티모달 입력과 함께 HF processor에 전달해 처리된 멀티모달 데이터를 얻는 구조예요.

마찬가지로, vLLM이 추출한 멀티모달 데이터가 특정 HF processor가 기대하는 것과 일치하지 않을 수 있어요. [_apply_hf_processor_main][vllm.multimodal.processing.BaseMultiModalProcessor._apply_hf_processor_main]은 각 모델이 [_preprocess_hf_mm_data][vllm.multimodal.processing.BaseMultiModalProcessor._preprocess_hf_mm_data](예: audios 키를 audio로 바꾸거나 sampling_rate 같은 추가 키워드 인자 주입)로 입력을, [_postprocess_hf_mm_data][vllm.multimodal.processing.BaseMultiModalProcessor._postprocess_hf_mm_data]로 출력을 적응시킬 수 있게 해줘요. 전체 메서드를 다시 구현할 필요가 없어요.

Prompt Update Detection

HF processor의 주된 책임 중 하나는 프롬프트를 플레이스홀더 토큰으로 업데이트하는 거예요. 예를 들면:

  • 문자열의 시작 부분에 피처 플레이스홀더 토큰(예: <image><image>...<image>, 개수는 피처 크기와 같음)을 삽입
  • 기존 입력 플레이스홀더 토큰(예: 단일 이미지용 <image>)을 피처 플레이스홀더 토큰(예: 개수가 피처 크기와 같은 <image><image>...<image>)으로 교체

어떤 토큰이 업데이트됐는지에 대한 정보는 플레이스홀더 피처 토큰과 멀티모달 입력 사이의 대응 관계를 찾는 핵심이에요.

입력 텍스트 없이 HF processor를 호출하므로, 이 업데이트를 우리가 직접 수행해야 해요. vLLM에서는 필요한 정보를 [_get_prompt_updates][vllm.multimodal.processing.BaseMultiModalProcessor._get_prompt_updates]에서 [PromptUpdate][vllm.multimodal.processing.PromptUpdate]로 표현하고, [_apply_prompt_updates][vllm.multimodal.processing.BaseMultiModalProcessor._apply_prompt_updates]로 적용해요.

일부 HF processor는 멀티모달 입력과 무관하게 프롬프트 자체를 변형하기도 해요(예: ChameleonProcessor가 채팅 모드에서 sep 토큰을 추가). 프롬프트 토큰도 HF processor를 거치지 않으므로, 이런 변형은 프롬프트 업데이트를 찾거나 적용하기 전에 [_postprocess_prompt][vllm.multimodal.processing.BaseMultiModalProcessor._postprocess_prompt]로 재현해요.

Processor 출력 캐싱

Qwen2-VL용 processor 같은 일부 HF processor는 매우 느려요. 이 문제를 완화하려고, 동일한 멀티모달 입력(예: 이미지)을 다시 처리하지 않도록 HF processor의 멀티모달 출력을 캐시해요.

새 데이터가 들어오면 먼저 어떤 항목이 캐시에 있고 어떤 항목이 없는지 확인해요. 빠진 항목을 하나의 배치로 HF processor에 넣고 캐시한 뒤, 기존 캐시 항목과 병합해요.

멀티모달 데이터 처리 속도 높이기

디바이스에서의 융합 정규화 (Fused Normalisation on the Device)

멀티모달 데이터 파이프라인(디코딩, 리사이징, 정규화, 리스케일링)을 가속화하기 위해, 무거운 수치 전처리를 CPU에서 GPU로 내리고 데이터 이동을 최적화해요.

GPU에서의 정규화·리스케일링 융합

전통적으로 CPU는 픽셀 값을 255로 나눈 뒤 평균을 빼고 표준편차로 나눴어요. 이 단계들을 하나의 연산으로 융합해 GPU에서 전부 실행하죠.

작동 방식: 전용 FusedInputNorm 모듈을 사용하는데, 이 모듈은 리스케일 팩터(1/255)를 레이어의 weightbias 파라미터에 직접 구워 넣어요. 스케일·빼기·나누기라는 세 단계를 따로 수행하는 대신, 모듈은 단일 아핀 변환 하나로 모든 걸 처리해요: y = x * weight + bias.

파라미터는 이렇게 설정돼요.

  • weight는 표준편차와 리스케일 팩터를 모두 제어
  • bias는 평균과 같은 리스케일 팩터로 데이터를 중심화

즉, 이 모듈은 원시 픽셀 값(0–255)을 받아 별도의 /255 단계 없이 바로 정규화된 값을 출력해요.

융합 정규화를 위한 최적화된 데이터 경로

디바이스에서 직접 융합 정규화를 수행하면 전체 전송 경로를 — Entrypoint에서 Engine Core를 거쳐 GPU 메모리까지 — **uint8**로 유지할 수 있어요. 이렇게 하면 PCIe 대역폭이 절반으로 줄고 CPU 메모리 사용량도 줄어들죠.

데이터가 GPU 메모리에 도달한 뒤에만 FusedInputNorm 레이어를 위해 fp32로 캐스팅하고(수치 정확성 보장), 이후 레이어를 위해 bf16으로 캐스팅해요 — 모두 GPU 안에서 처리하므로 호스트 쪽 변환이 없어요.

전체 경로: Entrypoint (uint8) → Engine Core (uint8) → GPU Memory (uint8) → GPU 로컬 fp32 FusedInputNormbf16 출력.

토글: mm_device_do_normalize

이 GPU 쪽 융합은 **mm_device_do_normalize**라는 설정 플래그로 제어돼요.

  • True면 정규화와 리스케일링이 FusedInputNorm 레이어로 GPU에서 수행되고, False면 기존 CPU 쪽 경로로 폴백해요.
  • 이 플래그는 지원하는 모든 모델에서 기본적으로 활성화돼요.
  • 현재 기본 활성화된 아키텍처는 다음과 같아요.
name Architecture Example HF Models
qwen2-vl Qwen2VLForConditionalGeneration Qwen/Qwen2-VL-2B-Instruct, etc.
qwen2.5-vl Qwen2_5_VLForConditionalGeneration Qwen/Qwen2.5-VL-3B-Instruct, etc.

전반적으로 얻는 것

  • CPU 오프로드: 정규화·리스케일링 연산이 CPU에서 완전히 사라져요.
  • PCIe 절약: bf16(2바이트) 대신 uint8(1바이트)를 보내므로 데이터 전송량이 50% 줄어요.
  • GPU 오버헤드: 융합 커널은 매우 가벼워서 종종 이후 CUDA 연산과 병합될 수 있어 거의 추가 비용이 없어요.

더 알아보기 (Learn more)