본문으로 건너뛰기

[vLLM] Multimodal: Vision, Audio, Video 처리 파이프라인

들어가며

최근 LLM은 텍스트뿐만 아니라 이미지, 오디오, 비디오를 함께 처리하는 멀티모달 모델로 진화하고 있다. vLLM은 vllm/multimodal/ 모듈에서 이러한 다양한 모달리티를 통합적으로 처리하는 프레임워크를 제공한다.

공식 문서

vLLM 공식 문서: Multimodal Inputs

핵심 구조/코드 분석

MultiModalRegistry

class MultiModalRegistry:
    def supports_multimodal_inputs(self, model_config: "ModelConfig") -> bool:
        if not model_config.is_multimodal_model:
            return False
        mm_config = model_config.get_multimodal_config()
        info = self._create_processing_info(model_config, tokenizer=None)
        if all(mm_config.get_limit_per_prompt(modality) == 0
               for modality in info.supported_mm_limits):
            if mm_config.enable_mm_embeds:
                return True  # 사전 계산된 임베딩은 여전히 처리 필요
            return False
        return True

레지스트리는 모델이 멀티모달을 지원하는지 판단한다. 모든 모달리티의 제한이 0이면 텍스트 전용 모드로 실행하되, enable_mm_embeds가 켜져 있으면 사전 계산된 임베딩 처리를 위해 멀티모달 인프라를 유지한다.

프로세서 팩토리 패턴

@dataclass(frozen=True)
class _ProcessorFactories(Generic[_I]):
    info: ProcessingInfoFactory[_I]
    processor: MultiModalProcessorFactory[_I]
    dummy_inputs: DummyInputsBuilderFactory[_I]

    def build_processor(self, ctx, *, cache=None):
        info = self.info(ctx)
        dummy_inputs_builder = self.dummy_inputs(info)
        return self.processor(info, dummy_inputs_builder, cache=cache)

세 가지 팩토리(정보, 프로세서, 더미 입력)를 조합하여 프로세서를 구성한다. 모델 클래스에 _processor_factory로 등록된다:

def register_processor(self, processor, *, info, dummy_inputs):
    def wrapper(model_cls):
        model_cls._processor_factory = _ProcessorFactories(
            info=info, dummy_inputs=dummy_inputs, processor=processor,
        )
        return model_cls
    return wrapper

캐싱 시스템

def _get_cache_type(self, vllm_config) -> Literal[None, "processor_only", "lru", "shm"]:
    mm_config = model_config.get_multimodal_config()
    if mm_config.mm_processor_cache_gb <= 0:
        return None
    is_ipc_supported = parallel_config._api_process_count == 1 and (
        parallel_config.data_parallel_size == 1
        or parallel_config.data_parallel_external_lb
    )
    if not is_ipc_supported:
        return "processor_only"
    return mm_config.mm_processor_cache_type

멀티모달 캐시는 네 가지 모드를 지원한다:

  • None: 캐시 비활성화
  • processor_only: 프로세서 결과만 캐시 (IPC 불가 환경)
  • lru: LRU 기반 프로세서+수신자 캐시
  • shm: 공유 메모리 기반 캐시 (ShmObjectStore)

모달리티별 미디어 처리

vllm/multimodal/media/
├── base.py          # 기본 미디어 인터페이스
├── image.py         # 이미지 처리
├── audio.py         # 오디오 처리
├── video.py         # 비디오 처리
└── connector.py     # 미디어 커넥터

각 모달리티의 원시 데이터를 모델이 이해하는 형식으로 변환한다. 이미지의 경우 리사이징, 정규화, 패치 분할 등을 수행한다.

더미 입력과 메모리 프로파일링

def get_dummy_mm_inputs(self, model_config, mm_counts, *, cache=None, processor=None):
    seq_len = model_config.max_model_len
    processor_inputs = processor.dummy_inputs.get_dummy_processor_inputs(
        seq_len=seq_len, mm_counts=mm_counts, mm_options=mm_config.limit_per_prompt,
    )
    mm_inputs = processor.apply(processor_inputs, timing_ctx=TimingContext(enabled=False))
    prompt_token_ids = mm_inputs["prompt_token_ids"]
    if len(prompt_token_ids) < seq_len:
        prompt_token_ids.extend([0] * (seq_len - len(prompt_token_ids)))
    return mm_inputs

메모리 프로파일링을 위해 최대 크기의 더미 멀티모달 입력을 생성한다. 이를 통해 GPU 메모리 예산을 정확하게 계산할 수 있다.

왜 이 설계인가

  1. 데코레이터 기반 모델 등록: @register_processor 데코레이터로 모델 클래스에 직접 프로세서를 바인딩한다. 이렇게 하면 모델 정의와 멀티모달 처리 로직이 함께 관리되어, 새 모델 추가 시 한 곳만 수정하면 된다.

  2. IPC 기반 캐시 분기: 데이터 병렬 환경에서는 여러 프로세스가 같은 이미지를 처리할 수 있다. SHM 캐시를 사용하면 한 번 처리한 결과를 공유 메모리로 다른 프로세스에 전달하여 중복 연산을 피한다. IPC가 불가능한 환경에서는 프로세서 전용 캐시로 폴백한다.

  3. 타이밍 컨텍스트: TimingContext로 멀티모달 처리 각 단계의 소요 시간을 측정할 수 있다. 이미지 전처리, 토큰화, 인코더 실행 등 어디서 병목이 발생하는지 관측 가능하다.

참고 자료

댓글

관련 포스트

vLLM 의 다른글