[SGLang] Multimodal 처리 파이프라인 개요: Vision/Audio/Video 통합
들어가며
최신 LLM은 텍스트뿐 아니라 이미지, 비디오, 오디오까지 이해한다. SGLang은 30개 이상의 멀티모달 프로세서를 통합하여 다양한 VLM, 음성 모델을 단일 파이프라인에서 서빙한다.
이 글에서는 python/sglang/srt/multimodal/ 디렉토리를 중심으로 멀티모달 처리의 전체 흐름을 분석한다.
전체 파이프라인 구조도
사용자 입력 (text + image/video/audio)
│
▼
┌──────────────────────────────────────────┐
│ BaseMultimodalProcessor │
│ ├── load_mm_data() │
│ │ ├── load_image() / load_video() │
│ │ └── load_audio() │
│ │ │
│ ├── process_and_combine_mm_data() │
│ │ ├── HF Processor 호출 │
│ │ └── MultimodalDataItem 생성 │
│ │ │
│ └── MultimodalProcessorOutput 반환 │
│ ├── input_ids: [1, 2, IMG, IMG, 3] │
│ └── mm_items: [DataItem(feature)] │
└──────────────────┬───────────────────────┘
│
▼
┌──────────────────────────────────────────┐
│ Model Forward │
│ ├── Vision Encoder (CLIP, ViT, etc.) │
│ ├── Audio Encoder (Whisper, etc.) │
│ └── Projection Layer │
│ pixel_values → vision_embeddings │
│ audio_features → audio_embeddings │
└──────────────────┬───────────────────────┘
│
▼
┌──────────────────────────────────────────┐
│ Embedding 결합 │
│ input_embeddings[IMG_offsets] = │
│ vision_embeddings │
│ → LLM Decoder로 전달 │
└──────────────────────────────────────────┘
핵심 코드 분석
BaseMultimodalProcessor: 공통 기반
모든 멀티모달 프로세서의 기반 클래스는 세 가지 핵심 자료구조를 정의한다.
@dataclasses.dataclass
class BaseMultiModalProcessorOutput:
input_text: str
images: Optional[list[Union[Image.Image, dict]]] = ...
videos: Optional[list[Union[torch.Tensor, dict]]] = ...
audios: Optional[list[Union[np.ndarray, dict]]] = ...
def organize_results(self):
return (
[(Modality.IMAGE, data) for data in self.images]
+ [(Modality.VIDEO, data) for data in self.videos]
+ [(Modality.AUDIO, data) for data in self.audios]
)
세 가지 모달리티(IMAGE, VIDEO, AUDIO)를 단일 출력에서 통합한다.
MultimodalSpecialTokens: 플레이스홀더 관리
각 모달리티의 특수 토큰과 정규식 패턴을 관리한다.
@dataclasses.dataclass
class MultimodalSpecialTokens:
image_token: Optional[Union[str, List[str]]] = None
video_token: Optional[Union[str, List[str]]] = None
audio_token: Optional[Union[str, List[str]]] = None
image_token_id: Optional[int] = None
video_token_id: Optional[int] = None
audio_token_id: Optional[int] = None
def build(self, processor):
self.convert_to_strs(processor)
self.parse_regex()
self.get_combined_regex()
return self
build() 호출 시 텍스트 토큰을 ID로 변환하고, 정규식 패턴을 컴파일한다. 프롬프트에서 <image>, <video>, <audio> 등의 플레이스홀더를 찾아 실제 멀티모달 데이터와 매핑한다.
mm_utils.py: 이미지 처리 유틸리티
LLaVA 계열 모델의 anyres(Any Resolution) 처리를 제공한다.
def select_best_resolution(original_size, possible_resolutions):
original_width, original_height = original_size
best_fit = None
max_effective_resolution = 0
min_wasted_resolution = float("inf")
for width, height in possible_resolutions:
scale = min(width / original_width, height / original_height)
downscaled_width = int(original_width * scale)
downscaled_height = int(original_height * scale)
effective_resolution = min(
downscaled_width * downscaled_height,
original_width * original_height,
)
wasted_resolution = (width * height) - effective_resolution
# 유효 해상도 최대화, 낭비 최소화
원본 이미지 비율을 보존하면서 가장 효율적인 해상도를 선택한다.
DP Sharded Vision Model
대규모 이미지 배치를 텐서 병렬로 분산 처리한다.
def run_dp_sharded_vision_model(image_input, vision_model):
num_chunks = image_input.shape[0]
mp_world_size = get_tensor_model_parallel_world_size()
num_chunks_per_rank = (num_chunks + mp_world_size - 1) // mp_world_size
rank = get_tensor_model_parallel_rank()
image_input_per_rank = image_input_padded[
rank * num_chunks_per_rank : (rank + 1) * num_chunks_per_rank
]
vision_embeddings = vision_model(image_input_per_rank)
vision_embeddings = tensor_model_parallel_all_gather(vision_embeddings, dim=0)
각 TP rank가 이미지의 일부를 인코딩하고, all_gather로 결과를 모은다.
MRoPE(Multi-Resolution Rotary Position Embedding) 모델에는 더 정교한 로드 밸런싱이 적용된다.
def get_dp_encoder_lb_assignment(sizes, num_gpus=2):
large_to_small_indices = sorted(
range(n_samples), key=lambda i: sizes[i], reverse=True
)
for idx in large_to_small_indices:
min_gpu = min(range(num_gpus), key=lambda i: gpu_loads[i])
gpu_assignments[min_gpu].append(idx)
gpu_loads[min_gpu] += sizes[idx]
큰 이미지부터 가장 여유 있는 GPU에 할당하는 greedy 알고리즘으로, 이미지 크기 차이로 인한 GPU 간 불균형을 최소화한다.
프로세서 디렉토리 구조
processors/
├── base_processor.py # BaseMultimodalProcessor
├── clip.py # CLIP 모델용
├── internvl.py # InternVL 모델용
├── llava.py # LLaVA 계열
├── qwen_vl.py # Qwen-VL 계열
├── qwen3_asr.py # Qwen3-ASR 음성
├── whisper.py # Whisper 음성
├── glmasr.py # GLM-ASR 음성
├── pixtral.py # Pixtral
├── gemma3.py / gemma4.py # Gemma 계열
└── ... (30+ 프로세서)
설계 근거
| 설계 선택 | 이유 |
|---|---|
| BaseMultimodalProcessor | 30+ 모델의 공통 로직(로드, 토큰화, 오프셋 계산) 재사용 |
| MultimodalSpecialTokens | 모델마다 다른 플레이스홀더를 통일된 인터페이스로 처리 |
| Modality enum | IMAGE/VIDEO/AUDIO를 타입 안전하게 구분 |
| greedy 로드밸런싱 | 이미지 크기가 다양한 배치에서 GPU 활용도 극대화 |
| anyres 처리 | 고해상도 이미지를 패치로 분할하여 ViT 입력 크기 제한 우회 |
관련 포스트
- Vision-Language 모델: CLIP, InternVL, LLaVA - 개별 VLM 프로세서 상세
- Audio 모델: Whisper, Qwen3-ASR - 음성 모델 프로세서 상세
- ViT CUDA Graph: Vision Encoder 가속 - 비전 인코더 최적화
- Efficient Vision Sampling: 이미지 토큰 압축 - 비디오 토큰 효율화
참고
관련 포스트
SGLang 의 다른글
- 이전글 [SGLang] Custom Logit Processor: 사용자 정의 로짓 처리
- 현재글 : [SGLang] Multimodal 처리 파이프라인 개요: Vision/Audio/Video 통합
- 다음글 [SGLang] Vision-Language 모델: CLIP, InternVL, LLaVA 프로세서
댓글