[sglang] H100에서 Qwen3-VL 단일 이미지 서빙 성능 11% 향상: SGLang 최적화 분석
PR 링크: sgl-project/sglang#36411 상태: Merged | 변경: +1370 / -76
들어가며: 단일 이미지 스트리밍을 위한 Qwen3-VL 서빙의 도전 과제
최근 멀티모달 대규모 언어 모델(MLLM)의 발전은 다양한 애플리케이션에서 새로운 가능성을 열어주고 있습니다. 특히 Qwen3-VL과 같은 모델은 이미지와 텍스트를 동시에 처리하여 복잡한 추론을 수행할 수 있습니다. 하지만 이러한 모델을 고성능으로 서빙하는 것은 상당한 기술적 도전 과제를 수반합니다.
이번에 분석할 sgl-project/sglang 레포지토리의 PR은 NVIDIA H100 GPU에서 Qwen3-VL 모델의 단일 이미지 스트리밍 서빙 성능을 획기적으로 개선하는 데 초점을 맞추고 있습니다. 기존 시스템은 주로 캐시-핫(cache-hot) 시나리오, 즉 동일한 이미지가 반복적으로 처리될 가능성이 있는 경우를 가정하여 설계되었습니다. 이로 인해 전처리 아티팩트에 16GiB, 멀티모달 임베딩에 8GiB와 같은 상당한 양의 메모리가 자동으로 예약되었고, 이는 단일 이미지 스트리밍과 같이 이미지가 한 번만 처리되는 트래픽에서는 불필요한 메모리 낭비로 이어졌습니다. 또한, 이러한 오버헤드는 실제 전처리, 피처 전송, 스케줄링 비용을 가려 성능 분석을 어렵게 만들었습니다.
이 PR의 목표는 이러한 비효율성을 제거하고, 고유한 이미지가 한 번만 처리되는 워크로드에 최적화된 Qwen3-VL 서빙 경로를 구축하여 H100 GPU의 잠재력을 최대한 활용하는 것입니다. 결과적으로 vLLM 대비 11.18% 향상된 68.551 requests/s의 처리량을 달성했습니다.
코드 분석: H100 최적화를 위한 핵심 변경사항
이 PR은 Qwen3-VL 모델의 단일 이미지 서빙을 최적화하기 위해 여러 파일에 걸쳐 정교한 변경사항을 도입했습니다. 주요 변경사항을 파일별로 살펴보겠습니다.
1. 설정 필드의 resolvable 속성 추가 (python/sglang/srt/arg_groups/fields/*.py)
PR 초기 단계에서 리뷰어 chuqiwang님은 특정 설정 필드들이 resolvable=True로 마크되지 않아 모델 오버라이드 시 시작 실패가 발생한다고 지적했습니다. BBuf님은 이 문제를 해결하기 위해 해당 필드들에 resolvable=True를 추가했습니다. 이는 모델별 기본값 오버라이드가 올바르게 작동하도록 하여, 특정 모델 및 하드웨어 구성에 맞는 최적화를 유연하게 적용할 수 있게 합니다.
Before (예시: radix_eviction_policy):
radix_eviction_policy: A[
Literal["lru", "priority"],
Arg(
help=(
"The policy to evict KV cache entries. See the documentation "
"for what each policy optimizes for."
),
choices=RADIX_EVICTION_POLICY_CHOICES,
),
] = "lru"
After:
radix_eviction_policy: A[
Literal["lru", "priority"],
Arg(
help=(
"The policy to evict KV cache entries. See the documentation "
"for what each policy optimizes for."
),
choices=RADIX_EVICTION_POLICY_CHOICES,
resolvable=True,
),
] = "lru"
mm_preprocess_cache_size_mb, mm_feature_transport, prefill_decode_interval 필드에도 동일하게 resolvable=True가 추가되어, 모델 오버라이드 시스템이 이 값들을 동적으로 변경할 수 있게 되었습니다. 이는 특히 Qwen3-VL과 같은 특정 모델에 대해 H100 환경에서 최적의 기본값을 설정하는 데 필수적입니다.
2. Qwen3-VL/Hopper 전용 서빙 오버라이드 (python/sglang/srt/arg_groups/model_overrides/qwen3_vl.py)
이 파일은 H100 GPU에서 Qwen3-VL 모델의 성능을 극대화하기 위한 핵심 로직을 담고 있습니다. _qwen3vl_hopper_serving_overrides 함수는 대형 Hopper GPU(SM90, 60GiB 이상)에서 Qwen3-VL 모델이 실행될 때 특정 최적화 설정을 자동으로 적용합니다.
핵심 변경 사항:
- 캐시 비활성화: 단일 이미지 스트리밍 워크로드의 특성을 반영하여, 불필요한 멀티모달 전처리 캐시와 임베딩 캐시를 비활성화합니다. (
mm_preprocess_cache_size_mb = 0,SGLANG_VLM_CACHE_SIZE_MB = 0) - CUDA IPC 피처 전송: 단일 노드 환경에서 멀티모달 피처 전송 방식을
cuda_ipc로 설정하여 CPU 오버헤드를 줄입니다. (mm_feature_transport = "cuda_ipc")- 리뷰 반영:
mickqian님의 지적에 따라,cuda_ipc는nnodes == 1일 때만 자동으로 적용되도록 수정되어 멀티 노드 환경에서의 호환성 문제를 해결했습니다.
- 리뷰 반영:
- IPC 풀 크기 증대: 고동시성(max_running_requests >= 400) 시나리오에서 CUDA IPC 풀(
SGLANG_MM_FEATURE_CACHE_MB)을 1GiB에서 3GiB로 늘려, CPU 폴백을 방지하고 GPU 상주 데이터를 유지합니다. - Radix Eviction 정책:
radix_eviction_policy를 기본lru에서priority로 변경하여 프롬프트 노드를 우선적으로 유지합니다. - Prefill/Decode 간격:
prefill_decode_interval을 22로 설정하여 프리필 및 디코드 배치 간의 균형을 최적화합니다. - 디코드 어텐션 백엔드:
decode_attention_backend를flashinfer로 설정하여 디코드 단계의 성능을 향상시킵니다.
Before (기본값 또는 이전 설정):
# ... (기존 기본값, 예를 들어 mm_preprocess_cache_size_mb=None, mm_feature_transport=None)
# ... (radix_eviction_policy='lru', prefill_decode_interval=0)
After (Qwen3-VL/Hopper 오버라이드 적용 시):
@_register_for(
"Qwen3VLForConditionalGeneration",
"Qwen3VLMoeForConditionalGeneration",
)
def _qwen3vl_hopper_serving_overrides(server_args: Any, hf_config: Any) -> dict:
# ... (중략)
if not envs.SGLANG_VLM_CACHE_SIZE_MB.is_set():
envs.SGLANG_VLM_CACHE_SIZE_MB.set(0)
updates = {}
preprocess_cache_size_mb = cfg.mm_preprocess_cache_size_mb
if preprocess_cache_size_mb is None:
preprocess_cache_size_mb = 0
updates["mm_preprocess_cache_size_mb"] = 0
cache_retention_enabled = (
preprocess_cache_size_mb > 0 or envs.SGLANG_VLM_CACHE_SIZE_MB.get() > 0
)
if (
cfg.mm_feature_transport is None
and cfg.nnodes == 1 # <-- Multi-node fix
and not cache_retention_enabled
):
updates["mm_feature_transport"] = "cuda_ipc"
# ... (중략)
if (
not envs.SGLANG_MM_FEATURE_CACHE_MB.is_set()
and cfg.max_running_requests is not None
and cfg.max_running_requests >= 400
and not cache_retention_enabled
and resolved_transport == "cuda_ipc"
):
envs.SGLANG_MM_FEATURE_CACHE_MB.set(3 * 1024)
if (
cfg.radix_eviction_policy == "lru"
and not cfg._radix_eviction_policy_explicitly_set
):
updates["radix_eviction_policy"] = "priority"
if cfg.prefill_decode_interval is None:
updates["prefill_decode_interval"] = 22
if cfg.attention_backend is None and cfg.decode_attention_backend is None:
updates["decode_attention_backend"] = "flashinfer"
# ... (중략)
return updates
3. 디코드 CUDA 그래프 확장 (python/sglang/srt/arg_groups/memory_hook.py)
handle_gpu_memory_settings 함수 내에서 expand_multimodal_decode_graph_to_running_limit 함수를 호출하여, Qwen3-VL 모델의 디코드 CUDA 그래프 max_bs를 max_running_requests까지 확장합니다. 이는 고동시성 환경에서 디코드 작업을 CUDA 그래프 내에서 효율적으로 처리할 수 있도록 합니다.
Before:
# ... (기존 CUDA graph batch-size materialization 로직)
if cfg.device != "cpu":
if decode_cuda_graph_config.bs is None:
decode_cuda_graph_config.bs = generate_decode_cuda_graph_batch_sizes(
decode_cuda_graph_config.max_bs
)
After:
# ... (기존 CUDA graph batch-size materialization 로직)
from sglang.srt.arg_groups.model_overrides.qwen3_vl import (
expand_multimodal_decode_graph_to_running_limit,
)
expand_multimodal_decode_graph_to_running_limit(
server_args, decode_cuda_graph_config, gpu_mem
)
# ------------------------------------------------------------------
# CUDA graph batch-size materialization
# ------------------------------------------------------------------
if cfg.device != "cpu":
if decode_cuda_graph_config.bs is None:
decode_cuda_graph_config.bs = generate_decode_cuda_graph_batch_sizes(
decode_cuda_graph_config.max_bs
)
4. 멀티모달 피처 처리 견고성 개선 (python/sglang/srt/models/qwen3_vl.py 관련 리뷰)
mickqian님은 qwen3_vl.py의 초기 변경사항에서 단일 이미지의 one-shot borrow 경로가 요청 철회(retracted request) 시 멀티모달 입력 피처를 지워버려 재연산이 필요한 경우 NoneType 오류를 발생시킬 수 있다고 지적했습니다. BBuf님은 이 문제를 해결하기 위해 one-shot borrow 경로를 제거하고, 첫 번째 materialization 시 요청 소유의 텐서를 유지하도록 수정했습니다. 또한, ViT 패스 후 IPC 리스를 해제하고, 재프리필(re-prefill)을 위해 핀(pinned)된 호스트 카피를 별도의 스트림에 보존하는 방식으로 개선되었습니다. 이는 시스템의 견고성을 크게 향상시키고, 복잡한 요청 흐름에서도 안정적인 동작을 보장합니다.
왜 이 최적화가 좋은가?
이 PR은 단순한 코드 변경을 넘어, 멀티모달 LLM 서빙의 성능과 효율성을 근본적으로 개선하는 여러 가지 중요한 이점을 제공합니다.
1. 획기적인 성능 향상
가장 명확한 이점은 측정된 성능 수치입니다. H100 GPU에서 Qwen3-VL 모델의 단일 이미지 스트리밍 워크로드에서 68.551 requests/s를 달성하여, 기존 vLLM의 61.657 requests/s 대비 11.18%의 성능 향상을 이루었습니다. 이는 실제 프로덕션 환경에서 더 많은 요청을 처리하고 사용자 경험을 개선하는 데 직접적으로 기여합니다.
2. 자원 효율성 극대화
- 메모리 낭비 제거: 단일 이미지 스트리밍 워크로드의 특성을 정확히 파악하여, 불필요한 전처리 및 임베딩 캐시를 비활성화함으로써 24GiB에 달하는 메모리 낭비를 방지했습니다. 이는 GPU 메모리가 제한적인 환경에서 특히 중요하며, 더 많은 모델을 로드하거나 더 큰 배치 크기를 처리할 수 있는 여유를 제공합니다.
- CUDA IPC 활용: 멀티모달 피처 전송에 CPU 메모리 대신 CUDA IPC를 사용함으로써, CPU-GPU 간 데이터 전송 오버헤드를 줄이고 GPU의 직접적인 데이터 접근을 가능하게 합니다. 이는 특히 대량의 이미지 데이터를 처리할 때 병목 현상을 완화합니다.
- 최적화된 IPC 풀: 고동시성 시나리오에서 IPC 풀 크기를 3GiB로 늘려, GPU 상주 데이터를 유지하고 CPU 폴백을 방지함으로써 지속적인 고성능을 유지합니다.
3. 하드웨어 활용 극대화 및 병목 현상 완화
- H100 특화 최적화: SM90 아키텍처(H100)의 특성을 고려한 모델 오버라이드를 통해, 해당 하드웨어에서 Qwen3-VL이 최적의 성능을 발휘하도록 세밀하게 튜닝되었습니다. FlashInfer 디코드 백엔드 사용, 최적화된 Prefill/Decode 간격 설정 등이 이에 해당합니다.
- CUDA Graph 활용: 고동시성 디코드 작업을 CUDA Graph 내에서 처리함으로써, 커널 런치 오버헤드를 줄이고 GPU 활용률을 높여 전체 처리량을 향상시킵니다.
- 세밀한 프로파일링: Nsight Compute 및 Torch Trace를 통한 심층적인 프로파일링 결과, BF16 nvJet GEMMs가 디코드 및 확장 시간의 70% 이상을 차지하며,
M=8과 같은 작은 배치 크기에서는 대역폭 제한이 있음을 확인했습니다. 이는 향후small-M게이트-업 경로 최적화와 같은 추가 개선 방향을 제시합니다.
4. 견고성 및 유연성 향상
- 리뷰 기반 개선: PR 과정에서 발생한 여러 기술적 피드백(예:
resolvable=True누락, 멀티 노드cuda_ipc문제, 요청 철회 시 데이터 손실)을 적극적으로 반영하여, 시스템의 안정성과 호환성을 크게 높였습니다. 이는 실제 운영 환경에서 발생할 수 있는 잠재적인 문제를 사전에 방지하는 데 중요합니다. - 모델별/하드웨어별 유연성:
resolvable=True속성 추가와 모델 오버라이드 시스템을 통해, SGLang이 다양한 모델과 하드웨어 구성에 유연하게 대응하고 최적의 성능을 제공할 수 있는 기반을 마련했습니다.
일반적 교훈
이 PR은 고성능 LLM 서빙 시스템을 구축하는 데 있어 몇 가지 중요한 교훈을 제공합니다.
- 워크로드 특성 이해: 시스템을 설계하고 최적화할 때, 대상 워크로드의 특성(예: 단일 이미지 스트리밍 vs. 반복 이미지 캐싱)을 명확히 이해하고 이에 맞는 전략을 수립하는 것이 중요합니다.
- 하드웨어-소프트웨어 공동 설계: 특정 하드웨어(예: H100)의 아키텍처적 특성을 최대한 활용하기 위해 소프트웨어 설정을 세밀하게 조정하는 것이 성능 향상에 결정적인 역할을 합니다.
- 심층적인 프로파일링:
Nsight Compute나Torch Trace와 같은 도구를 사용하여 시스템의 병목 현상을 정확하게 식별하고, 이를 기반으로 최적화 방향을 설정하는 것이 중요합니다. - 강력한 코드 리뷰 문화: 동료들의 기술적 피드백을 통해 초기 설계의 결함을 발견하고 개선함으로써, 시스템의 견고성과 안정성을 확보할 수 있습니다.
결론
SGLang의 이번 Qwen3-VL H100 최적화 PR은 멀티모달 LLM 서빙의 성능과 효율성을 한 단계 끌어올린 모범적인 사례입니다. 불필요한 자원 낭비를 줄이고, H100 GPU의 잠재력을 최대한 활용하며, 철저한 코드 리뷰를 통해 시스템의 견고성까지 확보했습니다. 이러한 접근 방식은 앞으로도 고성능 AI 서빙 시스템을 구축하는 데 중요한 지침이 될 것입니다.
참고 자료
- https://github.com/sgl-project/sglang
- https://huggingface.co/Qwen/Qwen3-VL-4B-Instruct
- https://github.com/flashinfer/flashinfer
⚠️ 알림: 이 분석은 AI가 실제 코드 diff를 기반으로 작성했습니다.
관련 포스트
- [sglang] Cutlass FP8 Blockwise GEMM 최적화: 불필요한 패딩 제거로 GPU 성능 향상
- [sglang] NVIDIA SM90 GPU를 위한 SGLang SubBlock Sparse Attention 최적화: Sage FP8 Compute 도입
- [sglang] SGLang LTX-2 모델을 위한 Breakable CUDA Graph 최적화 분석
- [sglang] SGLang 성능 최적화: 과도한 Prefill CUDA Graph Padding 방지로 TTFT 개선
- [vllm] vLLM, DeepStream NVDEC 백엔드로 비디오 디코딩 성능 혁신: GPU 가속의 힘
PR Analysis 의 다른글
- 이전글 [vllm] vLLM Elastic EP의 CUDA Graph 재사용을 통한 리컨피규레이션 다운타임 97% 절감
- 현재글 : [sglang] H100에서 Qwen3-VL 단일 이미지 서빙 성능 11% 향상: SGLang 최적화 분석
- 다음글 [flashinfer] NVIDIA Blackwell 아키텍처를 위한 고성능 BF16 x FP4 GEMM 커널 최적화
댓글