[onnxruntime] [CUDA] HyperConnectionLowRankMix 최적화: Speculative Decoding과 Hopper 아키텍처를 위한 튜닝
PR 링크: microsoft/onnxruntime#33548 상태: Merged | 변경: +503 / -166
들어가며
LLM 추론 속도를 높이기 위한 기법 중 하나인 Speculative Decoding(추측 디코딩)은 한 번의 스텝에서 여러 개의 토큰($S$)을 동시에 검증합니다. 이 과정에서 $S \le 8$ 수준의 작은 시퀀스 길이에 대한 연산 효율성이 전체 처리량(Throughput)에 큰 영향을 미칩니다.
최근 Microsoft의 ONNX Runtime 레포지토리에 반영된 PR은 HyperConnectionLowRankMix 커널을 최적화하여, 특히 NVIDIA Hopper(SM90) 아키텍처에서의 성능을 극대화하고 Speculative Decoding 시나리오($S=8$)에서의 효율을 개선했습니다. 이번 글에서는 실제 코드 변경 사항을 통해 어떤 최적화 기법들이 적용되었는지 살펴보겠습니다.
코드 분석: 핵심 변경 사항
1. Fused Row-Group Dispatch (4x2 전략)
기존에는 행(row) 수에 관계없이 고정된 방식으로 커널을 런칭했으나, 이번 PR에서는 $S=8$인 경우를 위해 4x2 Fused Row-Group 전략을 도입했습니다. 이는 HcDownKernel에서는 CTA당 4개 행을 처리하고, HcUpPreKernel에서는 CTA당 2개 행을 처리하도록 분배하여 하드웨어 리소스를 최적으로 활용합니다.
Before:
// 기존에는 행 수에 따른 세밀한 분기가 부족했음
LaunchFused<T>(...);
After:
// docs/contrib_ops/cuda/hyper_connection_low_rank_mix.md 발췌
// rows에 따른 최적의 row group 설정
| rows | row groups |
| ---- | ---------- |
| 1 | 1 x 1 |
| 2..7 | 2 x 2 |
| 8 | 4 x 2 |
이러한 정교한 디스패치는 특히 $S=8$인 Speculative Decoding 환경에서 H200 기준 약 0.04~0.08ms의 성능 이득을 가져왔습니다.
2. Hopper PDL (Programmatic Dependent Launch) 도입
NVIDIA Hopper 아키텍처(SM90)와 CUDA 12.3+ 환경에서는 커널 간의 의존성을 하드웨어 수준에서 더 효율적으로 관리할 수 있는 PDL(Programmatic Dependent Launch) 기능을 지원합니다. 이를 통해 HcDownKernel과 HcUpPreKernel 사이의 스트림 직렬화를 최적화했습니다.
Code Snippet (Environment Variable):
// onnxruntime/contrib_ops/cuda/bert/hyper_connection_low_rank_mix_impl.cu
#include "core/platform/env_var_utils.h"
// ORT_HYPER_CONNECTION_PDL 환경 변수를 통해 제어
// SM90+에서 cudaLaunchAttributeProgrammaticStreamSerialization 활성화
또한, 가중치(weight)가 상수(constant initializer)인 경우 __ldcg(load cache global)를 사용하여 캐시 효율을 높였습니다.
Before/After (Memory Access):
// Before
sv[r][i] = qi < nvec ? sp[qi] : make_uint4(0u, 0u, 0u, 0u);
// After (__ldcg 적용으로 캐시 히트율 향상)
sv[i] = qi < nvec ? __ldcg(sp + qi) : make_uint4(0u, 0u, 0u, 0u);
3. Register Spill 방지 및 Rank-320 특화
HcUpPreKernel에서 Rank가 320인 경우를 특화(Specialization)하여, 로컬 레지스터 배열(float4 pq[8][ROWS])을 사용하는 대신 부분합을 직접 누적하도록 수정했습니다. 이는 GPU의 한정된 자원인 레지스터 사용량을 줄여 Register Spill을 방지하고 점유율(Occupancy)을 높이는 효과가 있습니다.
Before (Register Array 사용):
float4 pq[8][ROWS]; // 레지스터 압박 유발 가능성
// ... 연산 후 누적
After (Direct Accumulation):
// HcUpPreKernel<T, ROWS, 4, 320> 특화 버전
// va[r]에 직접 K-split partial sums를 누적하여 레지스터 절약
4. Deterministic Unfused Path 및 Branch Tiling
기존의 Unfused Path는 cuBLAS GEMM을 사용했으나, 이번 업데이트에서는 결정론적(Deterministic) 결과를 보장하기 위해 직접 구현한 FusedInjectPostGateKernel로 교체되었습니다. 특히 리뷰어들의 피드백을 반영하여 8개 이상의 브랜치(num_branches > 8)도 처리할 수 있도록 grid.y 방향으로 타일링(Tiling)을 적용했습니다.
Reviewer Feedback 반영:
"num_branches가 8보다 큰 경우에도 대응할 수 있도록 타일링을 적용하고 테스트 케이스를 추가해달라."
After (Tiling Logic):
// FusedInjectPostGateKernel에서 8개 브랜치 단위로 타일링 수행
// grid.y를 통해 arbitrary num_branches 지원
또한, 불필요한 스크래치 버퍼 할당을 제거하여 메모리 오버헤드를 줄였습니다.
Before/After (Memory Management):
// onnxruntime/contrib_ops/cuda/bert/hyper_connection_low_rank_mix.cc
// Before
auto inject = GetScratchBuffer<T>(rows * static_cast<size_t>(branches), GetComputeStream(context));
// After
// inject 스크래치 버퍼 할당 로직 삭제 (커널 퓨전으로 불필요해짐)
왜 이게 좋은 최적화인가?
- 아키텍처 특화 활용: 단순히 범용 코드를 작성하는 것에 그치지 않고, Hopper 아키텍처의 PDL이나
__ldcg같은 최신 기능을 적극적으로 활용하여 하드웨어 잠재력을 끌어올렸습니다. - 워크로드 맞춤형 설계: Speculative Decoding에서 자주 발생하는 $S=8$ 케이스를 타겟팅하여
4x2Row-Group 분배를 설계한 점은 실전 성능에 큰 영향을 미칩니다. 벤치마크 결과, 기본 설정 대비 약 1% 이상의 성능 향상을 확인했습니다. - 안정성과 확장성: 리뷰어의 지적을 수용하여 브랜치 수 제한을 풀고 타일링을 도입함으로써, 최적화와 범용성 사이의 균형을 맞췄습니다. 또한 Deterministic 연산을 보장하여 모델 재현성 문제를 해결했습니다.
일반적인 교훈
- Small Batch/Sequence 최적화: LLM 서비스 환경에서는 큰 배치보다 작은 배치나 짧은 시퀀스의 지연 시간(Latency)이 중요할 때가 많습니다. 이때는 커널 런칭 오버헤드와 리소스 분배가 핵심입니다.
- Register Pressure 관리: CUDA 커널에서 배열을 로컬 변수로 선언할 때는 항상 Register Spill 가능성을 염두에 두어야 합니다. 특수 케이스에 대한 Specialization은 이를 해결하는 좋은 방법입니다.
이 PR은 최신 GPU 아키텍처의 기능을 이해하고, 실제 서비스 시나리오(Speculative Decoding)의 특성을 반영한 수준 높은 최적화 사례라고 할 수 있습니다.
참고 자료
- https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#programmatic-dependent-launch-and-synchronization
- https://docs.nvidia.com/cuda/parallel-thread-execution/index.html#data-movement-and-conversion-instructions-ld
- https://github.com/microsoft/onnxruntime/blob/main/docs/contrib_ops/cuda/hyper_connection_low_rank_mix.md
⚠️ 알림: 이 분석은 AI가 실제 코드 diff를 기반으로 작성했습니다.
관련 포스트
- [onnxruntime] ONNX Runtime: fpA_intB GEMM 최적화 및 CUDA 그래프 호환성 강화
- [flashinfer] FlashInfer MoE 최적화: Ampere 및 Hopper 아키텍처에서 그룹화된 활성화 스테이징 오버랩
- [flashinfer] FlashInfer의 SM120 FP8 Attention 커널 최적화: Early PV Hand-off와 Next-unit Prefetch
- [flashinfer] FlashInfer의 CAKE GEMM 커널 최적화: 코드 중복 제거를 통한 성능 및 유지보수성 향상
- [flashinfer] FlashInfer SM90 아키텍처 성능 최적화: QkvNormRopeExchangeOverlap의 Shape Gate 개선
PR Analysis 의 다른글
- 이전글 [flashinfer] FlashInfer MoE 최적화: Ampere 및 Hopper 아키텍처에서 그룹화된 활성화 스테이징 오버랩
- 현재글 : [onnxruntime] [CUDA] HyperConnectionLowRankMix 최적화: Speculative Decoding과 Hopper 아키텍처를 위한 튜닝
- 다음글 [flashinfer] FlashInfer, CuTe DSL W4A16 MoE 최적화 및 비트 단위 정확도 테스트 추가
댓글