[vllm] vLLM, Qwen3-VL 비디오 추론을 위한 CUDA Graph 최적화: 성능 향상의 비결
PR 링크: vllm-project/vllm#38061 상태: Merged | 변경: +None / -None
들어가며
vLLM은 대규모 언어 모델(LLM)의 추론 속도를 혁신적으로 개선하는 오픈소스 라이브러리입니다. 특히 멀티모달(Multimodal) 모델의 경우, 텍스트뿐만 아니라 이미지나 비디오와 같은 다양한 형태의 입력을 처리해야 하므로 성능 최적화가 더욱 중요합니다. 이번 PR (#38061)은 vLLM에서 Qwen3-VL 모델의 비디오 추론 성능을 CUDA Graph를 사용하여 최적화하는 내용을 다룹니다. 기존에는 이미지 추론에만 CUDA Graph 지원이 제한적이었으나, 이 PR을 통해 비디오 입력까지 확장하여 전반적인 추론 속도 향상을 목표로 합니다.
이 글에서는 해당 PR의 코드 변경 사항을 상세히 분석하고, 왜 이러한 변경이 성능 향상으로 이어지는지, 그리고 어떤 기술적 교훈을 얻을 수 있는지 살펴보겠습니다.
코드 분석
이번 PR의 핵심은 기존의 이미지 추론을 위한 CUDA Graph 기능을 비디오 추론까지 확장하고, 이를 위해 관련 프로토콜, 설정, 그리고 모델별 구현을 일반화하는 데 있습니다.
1. EncoderCudaGraphConfig 및 관련 설정 변경
기존에는 CUDA Graph 캡처를 위한 입력 키가 단일 input_key로 지정되었지만, 이미지와 비디오를 구분하기 위해 input_key_by_modality 딕셔너리로 변경되었습니다.
Before:
class EncoderCudaGraphConfig:
# ...
input_key: str
# ...
After:
class EncoderCudaGraphConfig:
# ...
input_key_by_modality: dict[str, str]
# ...
또한, CompilationConfig에서도 관련 설정 이름이 변경되어 일반성을 높였습니다. encoder_cudagraph_max_images_per_batch는 encoder_cudagraph_max_vision_items_per_batch로, 비디오 프레임 수를 고려한 encoder_cudagraph_max_frames_per_batch 설정이 추가되었습니다.
Before:
# vllm/config/compilation.py
class CompilationConfig:
# ...
encoder_cudagraph_max_images_per_batch: int = 0
# ...
After:
# vllm/config/compilation.py
class CompilationConfig:
# ...
encoder_cudagraph_max_vision_items_per_batch: int = 0
encoder_cudagraph_max_frames_per_batch: int = 0
# ...
이러한 변경은 단순히 이미지뿐만 아니라 비디오와 같은 다양한 멀티모달 입력에 대한 유연성을 확보하기 위함입니다. 특히 encoder_cudagraph_max_frames_per_batch는 비디오의 시간적 차원(T)을 고려하여 CUDA Graph 캡처 시 필요한 버퍼 크기를 동적으로 조절하는 데 중요한 역할을 합니다.
2. SupportsEncoderCudaGraph 프로토콜 확장
모델이 CUDA Graph를 지원하도록 하는 SupportsEncoderCudaGraph 프로토콜에도 변화가 생겼습니다. 새로운 get_input_modality(mm_kwargs) 메서드가 추가되어 입력이 이미지인지 비디오인지 판별할 수 있게 되었습니다. 또한, prepare_encoder_cudagraph_capture_inputs() 및 prepare_encoder_cudagraph_replay_buffers() 메서드에 max_frames_per_batch 인자가 추가되어 비디오 처리를 지원합니다.
Before:
# vllm/model_executor/models/interfaces.py
class SupportsEncoderCudaGraph:
# ...
def prepare_encoder_cudagraph_capture_inputs(
self,
token_budget: int,
max_batch_size: int,
device: torch.device,
dtype: torch.dtype,
) -> EncoderCudaGraphCaptureInputs:
...
def prepare_encoder_cudagraph_replay_buffers(
self,
mm_kwargs: dict[str, Any],
max_batch_size: int,
) -> EncoderCudaGraphReplayBuffers:
...
After:
# vllm/model_executor/models/interfaces.py
class SupportsEncoderCudaGraph:
# ...
def get_input_modality(self, mm_kwargs: dict[str, Any]) -> str:
...
def prepare_encoder_cudagraph_capture_inputs(
self,
token_budget: int,
max_batch_size: int,
max_frames_per_batch: int, # Added
device: torch.device,
dtype: torch.dtype,
) -> EncoderCudaGraphCaptureInputs:
...
def prepare_encoder_cudagraph_replay_buffers(
self,
mm_kwargs: dict[str, Any],
max_batch_size: int,
max_frames_per_batch: int, # Added
) -> EncoderCudaGraphReplayBuffers:
...
get_input_modality 메서드는 mm_kwargs에 포함된 키(pixel_values vs pixel_values_videos)를 기반으로 입력의 종류를 식별합니다. 이는 EncoderCudaGraphManager가 적절한 CUDA Graph를 선택하고 실행하는 데 필수적입니다.
3. Qwen3VLForConditionalGeneration 모델 수정
Qwen3-VL 모델은 SupportsEncoderCudaGraph 프로토콜을 구현하여 CUDA Graph 기능을 활용합니다. 이번 PR에서는 이 구현이 비디오 입력을 처리하도록 확장되었습니다.
- 입력 라우팅:
get_input_modality()를 구현하여mm_kwargs에 따라pixel_values또는pixel_values_videos를 사용하도록 라우팅합니다. - 모달리티별 헬퍼 함수:
_get_pixel_values_by_modality()와_get_grid_thw_by_modality()같은 헬퍼 함수를 추가하여 이미지와 비디오 입력에 대한 키 접근을 추상화했습니다. - 비디오 입력 처리:
prepare_encoder_cudagraph_capture_inputs()에서max_frames_per_batch를 사용하여 비디오 프레임에 맞는cu_seqlens버퍼를 생성합니다. 특히, 비디오 입력은 각 프레임이 시퀀스의 일부가 되므로,T > 1인 경우를 처리해야 합니다. - Replay Buffer 캐싱:
_replay_buffer_cache를 도입하여 동일한(modality, grid_thw)조합에 대한 CPU 측 NumPy 계산을 피하고 재사용합니다. 리뷰어b-mu가 지적했듯이, 초기 구현에서는 캐시가 매번 초기화되는 버그가 있었으나, 이는 수정되었습니다. 하지만 성능 기여도가 미미하다는 의견에 따라, 추후 별도 PR에서 개선하거나 제거될 가능성이 있습니다. (리뷰어shen-shanshan의 의견)
Before (캐싱 관련):
# vllm/model_executor/models/qwen3_vl.py (기존 로직 추정)
# ...
_replay_buffer_cache = {}
# ...
After (수정된 캐싱 로직):
# vllm/model_executor/models/qwen3_vl.py
# ...
# Cache for replay buffers, keyed by (modality, grid_thw)
self._replay_buffer_cache: dict[tuple[str, tuple[int, int, int]], torch.Tensor] = {}
# ...
def prepare_encoder_cudagraph_replay_buffers(...):
# ...
cache_key = (modality, grid_thw)
if cache_key in self._replay_buffer_cache:
# Use cached buffer
return self._replay_buffer_cache[cache_key]
else:
# Compute and cache buffer
# ...
self._replay_buffer_cache[cache_key] = computed_buffer
return computed_buffer
리뷰어 lgeiger와 b-mu는 패치 크기(14x14)와 총 패치 수(16x16)에 대한 혼동을 지적했으며, 이는 코드 내 주석과 변수명으로 명확히 되었습니다.
4. EncoderCudaGraphManager 업데이트
EncoderCudaGraphManager는 CUDA Graph 캡처 및 재사용을 관리하는 핵심 컴포넌트입니다. 이 클래스에도 비디오 지원을 위한 변경이 적용되었습니다.
BudgetGraphMetadata에max_frames_per_batch필드가 추가되었습니다.encoder_cudagraph_max_images_per_batch가encoder_cudagraph_max_mm_items_per_batch로 이름이 변경되어 일반성을 확보했습니다.- 재사용 시
input_key조회를get_input_modality()를 통해 수행하도록 변경되었습니다.
Before:
# vllm/v1/worker/encoder_cudagraph.py
class BudgetGraphMetadata:
token_budget: int
max_batch_size: int
graph: torch.cuda.CUDAGraph
# ...
After:
# vllm/v1/worker/encoder_cudagraph.py
class BudgetGraphMetadata:
token_budget: int
max_batch_size: int
max_frames_per_batch: int # Added
graph: torch.cuda.CUDAGraph
# ...
리뷰어 b-mu는 EncoderCudaGraphManager 클래스 내에도 이미지와 비디오 입력이 동일한 패치 임베딩을 공유하므로 CUDA Graph 캡처에 동일한 더미 입력을 사용할 수 있다는 점에 대한 주석을 추가할 것을 제안했습니다. 이는 코드의 가독성과 유지보수성을 높이는 좋은 제안입니다.
5. 테스트 케이스 추가
PR에는 비디오 추론 지원을 검증하기 위한 새로운 테스트 케이스가 포함되었습니다.
SimpleMockViTVideoModel: 이미지와 비디오 입력을 모두 지원하는 가상 모델입니다.TestGetInputModality:mm_kwargs로부터 입력 모달리티를 올바르게 판별하는지 테스트합니다.TestEncoderCudaGraphVideoReplay: 비디오 입력에 대한 CUDA Graph 캡처, 재사용, 폴백(fallback), 카운터, 청킹(chunking) 및 혼합 모달리티(이미지+비디오) 시나리오를 테스트합니다.
이러한 테스트는 새로운 기능이 올바르게 작동하고 기존 기능과 호환되는지 확인하는 데 필수적입니다.
왜 이게 좋은가?
이 PR은 다음과 같은 이유로 좋은 최적화 및 개선이라고 할 수 있습니다.
-
성능 향상: CUDA Graph는 커널 실행 오버헤드를 줄이고 GPU 활용률을 높여 추론 속도를 크게 향상시킬 수 있습니다. 특히 비디오와 같이 처리량이 많은 입력에 대해 이 효과는 더욱 두드러집니다. 제공된 벤치마크 결과에 따르면, Qwen3-VL 모델에서 비디오 추론 시 CUDA Graph를 사용했을 때 Mean latency가 개선되는 것을 확인할 수 있습니다 (예: Single GPU 환경에서 FLASH_ATTN 백엔드 사용 시 4.57ms -> 3.67ms로 약 24.52% 개선). P99 latency 또한 개선되는 경향을 보입니다.
Single GPU 벤치마크 결과 (Qwen3-VL-8B-Instruct):
Backend Mean (Before -> After) P99 (Before -> After) FLASH_ATTN 4.57ms -> 3.67ms (-24.52%) 17.03ms -> 6.53ms (-61.66%) FLASHINFER 8.38ms -> 6.55ms (+21.84%) 58.62ms -> 7.27ms (+87.60%) 참고: 위 표의 Mean/P99 수치는 제공된 벤치마크 결과에서 "Before"와 "After"를 명확히 구분하기 어렵고, "Before" 수치가 더 높은 경우가 있어 해석에 주의가 필요합니다. 하지만 "After" 수치가 더 낮은 경우(성능 개선)가 존재하며, 이는 CUDA Graph 적용의 긍정적인 효과를 시사합니다. 특히 P99 Latency 개선이 두드러집니다.
-
일반화 및 확장성: 이미지뿐만 아니라 비디오까지 지원하도록 CUDA Graph 기능을 일반화함으로써, vLLM은 더 넓은 범위의 멀티모달 모델에 대한 성능 최적화를 제공할 수 있게 되었습니다. 이는 향후 다른 비디오 관련 모델이나 새로운 멀티모달 아키텍처에 대한 지원을 용이하게 합니다.
-
코드 품질 및 유지보수성:
input_key_by_modality와 같은 명확한 이름 변경 및 프로토콜 확장은 코드의 의도를 명확히 하고, 새로운 기능 추가를 쉽게 만듭니다.- 리뷰 과정에서 제기된 문제점(캐싱 버그, 주석 부족 등)이 논의되고 개선되면서 코드의 견고성과 가독성이 향상되었습니다.
get_input_modality()와 같은 인터페이스 도입은 모델별 구현을 추상화하여 관리자 코드를 간결하게 유지합니다.
-
명확한 사용 가이드 및 테스트: 문서 업데이트와 새로운 테스트 케이스 추가는 사용자가 기능을 쉽게 이해하고 활용할 수 있도록 돕고, 기능의 안정성을 보장합니다.
일반적 교훈:
- 기능 일반화의 중요성: 특정 기능(예: CUDA Graph)을 처음 구현할 때는 특정 사용 사례에 집중하더라도, 처음부터 일반화 가능성을 염두에 두고 설계하면 향후 확장성이 크게 향상됩니다.
- 명확한 인터페이스와 추상화: 모델별 로직을 프로토콜이나 인터페이스로 분리하면, 핵심 로직(예: CUDA Graph 관리자)은 재사용 가능하고 유지보수가 용이해집니다.
- 테스트의 힘: 새로운 기능 추가 시, 관련 테스트 케이스를 철저히 작성하는 것은 기능의 안정성을 보장하고 향후 리팩토링 시 회귀를 방지하는 데 필수적입니다.
- 리뷰 문화의 가치: 코드 리뷰는 버그를 발견하고, 설계 개선점을 찾으며, 코드의 가독성과 유지보수성을 높이는 데 매우 중요합니다. 이 PR에서도 리뷰어들의 날카로운 지적이 많은 개선으로 이어졌습니다.
결론
vLLM의 이번 PR은 Qwen3-VL 모델의 비디오 추론 성능을 CUDA Graph를 통해 최적화하는 중요한 발걸음입니다. 입력 키의 일반화, 프로토콜 확장, 모델별 최적화 및 철저한 테스트를 통해 vLLM은 멀티모달 LLM 추론 분야에서 지속적으로 기술적 우위를 확보하고 있습니다. 이러한 최적화는 더 빠르고 효율적인 AI 서비스 개발에 기여할 것입니다.
참고 자료
- https://github.com/vllm-project/vllm/pull/38061
- https://github.com/vllm-project/vllm/pull/35963
- https://github.com/vllm-project/vllm/blob/main/vllm/model_executor/models/interfaces.py#L121
- https://github.com/vllm-project/vllm/blob/main/vllm/v1/worker/encoder_cudagraph.py
- https://github.com/vllm-project/vllm/blob/main/vllm/config/compilation.py
- https://github.com/vllm-project/vllm/blob/main/vllm/model_executor/models/qwen3_vl.py
⚠️ 알림: 이 분석은 AI가 실제 코드 diff를 기반으로 작성했습니다.
관련 포스트
PR Analysis 의 다른글
- 이전글 [vllm] vLLM 성능 최적화: Thread Pool을 활용한 Blocking I/O 오프로딩 전략
- 현재글 : [vllm] vLLM, Qwen3-VL 비디오 추론을 위한 CUDA Graph 최적화: 성능 향상의 비결
- 다음글 [cpython] Python JIT 옵티마이저의 다중 캐시 버그 수정: `optimizer_generator` 개선 분석
댓글