[onnxruntime] ONNX Runtime WebGPU: Reduce 연산 최적화를 통한 성능 향상
PR 링크: microsoft/onnxruntime#28174 상태: Merged | 변경: +15 / -9
들어가며
최근 대규모 언어 모델이나 비전 모델을 웹 환경에서 실행할 때, WebGPU의 성능은 사용자 경험을 결정짓는 핵심 요소입니다. Microsoft의 ONNX Runtime(ORT)은 다양한 하드웨어 가속을 지원하는데, 이번 PR은 WebGPU 백엔드에서 ReduceMean과 같은 Reduction 연산이 수행될 때 발생하는 비효율적인 메모리 복사 및 Transpose 문제를 해결합니다. 특정 조건에서 'Shared reduction' 방식이 오히려 성능 저하를 유발하는 문제를 발견하고, 이를 'Naive reduction'으로 전환하는 휴리스틱을 도입하여 성능을 크게 개선했습니다.
코드 분석
onnxruntime/core/providers/webgpu/reduction/reduction_ops.cc
기존 코드에서는 Reduction 연산 시 무조건적으로 Shared reduction 방식을 우선시하거나, 복잡한 조건 분기를 통해 연산을 수행했습니다. 특히, 입력 텐서의 차원이 정렬되어 있지 않을 경우, Shared reduction을 위해 Transpose 연산이 선행되어야 했는데, 이 비용이 매우 컸습니다.
Before:
bool use_naive_reduction = name_ == "ArgMin" || name_ == "ArgMax" || (reduce_size < 32 && output_size > 1024) || is_input_empty || input_tensor->Shape().NumDimensions() == 0;
After:
constexpr size_t kReduceNaiveMaxReduceSize = 128;
constexpr size_t kReduceNaiveMinOutputSize = 20000;
// ... (axes 확인 로직) ...
bool use_naive_reduction = name_ == "ArgMin" || name_ == "ArgMax" || (reduce_size < 32 && output_size > 1024) ||
(!are_axes_innermost && reduce_size <= kReduceNaiveMaxReduceSize &&
output_size > kReduceNaiveMinOutputSize) ||
is_input_empty || input_tensor->Shape().NumDimensions() == 0;
핵심 변경 사항은 !are_axes_innermost 조건이 추가된 것입니다. 즉, Reduction 대상 축이 메모리상에서 연속적이지 않을 때(innermost가 아닐 때), Transpose를 강제하는 Shared 방식 대신 Naive 방식을 선택하도록 휴리스틱을 개선했습니다. 또한, kReduceNaiveMaxReduceSize와 kReduceNaiveMinOutputSize라는 임계값을 도입하여, 특정 크기 이상의 연산에서만 이 최적화가 동작하도록 제어했습니다.
왜 이게 좋은가
이번 최적화의 핵심은 '불필요한 데이터 이동 최소화'입니다. 리뷰어 xhcao의 데이터에 따르면, florence-2-base-vision-encoder 모델 실행 시 ReduceMean 노드에서 발생하는 Transpose 연산이 전체 추론 시간의 상당 부분을 차지했습니다.
- 성능 수치: 기존 방식 대비
ReduceMean연산 자체의 시간은 0.30ms에서 0.11ms로 약 60% 이상 단축되었으며, 전체 모델 추론 시간은 약 340ms에서 270ms로 약 20% 향상되었습니다. - 교훈: GPGPU 프로그래밍에서 커널의 복잡도(Shared memory 활용 등)보다 더 중요한 것은 메모리 레이아웃입니다. 데이터가 정렬되지 않아 발생하는
Transpose비용은 커널 자체의 연산 효율보다 훨씬 클 수 있습니다. 따라서, 하드웨어의 특성에 맞는 적절한 알고리즘 선택(Naive vs Shared)이 성능 최적화의 핵심임을 보여줍니다.
리뷰 과정에서 qjia7은 이 로직을 특정 연산에 국한하지 않고 모든 Reduce 연산에 적용할 것을 제안했고, 결과적으로 더 범용적인 최적화 코드가 완성되었습니다. 이는 코드의 일관성을 유지하면서도 성능을 극대화하는 좋은 사례입니다.
참고 자료
⚠️ 알림: 이 분석은 AI가 실제 코드 diff를 기반으로 작성했습니다.
관련 포스트
- [onnxruntime] WebGPU 성능 최적화: Graph Capture 재사용을 위한 Session-level Buffer Pool 도입
- [onnxruntime] ONNX Runtime CPU ScatterElements 커널의 멀티스레딩 최적화 분석
- [triton] Triton Autotuner 최적화: Pruned Config가 하나일 때 불필요한 벤치마크 생략하기
- [vllm] vLLM Mooncake KV 오프로딩 최적화: 불필요한 KV 조회 건너뛰기
- [cpython] CPython unicodedata.normalize() 최적화: Py_UCS4 버퍼 직접 조작으로 성능 향상
PR Analysis 의 다른글
- 이전글 [sglang] Ascend NPU에서 Qwen3 모델을 위한 W8A8 MXFP8 양자화 지원
- 현재글 : [onnxruntime] ONNX Runtime WebGPU: Reduce 연산 최적화를 통한 성능 향상
- 다음글 [vllm] vLLM에서 Flashinfer 기반 Non-gated MoE bf16 지원 최적화 분석
댓글