[onnxruntime] ONNX Runtime CUDA Graph: 진정한 비동기 추론을 위한 동기화 지점 제거
PR 링크: microsoft/onnxruntime#28686 상태: Merged | 변경: +574 / -26
들어가며
지연 시간(Latency)에 민감한 고성능 딥러닝 추론 파이프라인을 구축할 때, 엔지니어들은 보통 다음의 네 가지 기법을 조합하여 최적의 성능을 끌어냅니다.
- IO Binding: 호스트-디바이스 간 데이터 복사 비용을 줄이기 위해 미리 할당된 GPU 버퍼를 사용합니다.
- Custom Compute Stream: 별도의 CUDA 스트림을 할당하여 연산을 격리합니다.
- CUDA Graph: 커널 런칭 오버헤드를 줄이기 위해 연산 그래프를 캡처하여 재사용합니다.
- Fully Async Execution:
Session::Run()호출 시 호스트 측의 동기화(Synchronization)를 완전히 제거하여 CPU가 다른 작업을 병렬로 수행하게 합니다.
하지만 지금까지 ONNX Runtime(ORT)에서는 disable_synchronize_execution_providers=1 옵션을 설정하더라도, CUDA Graph를 사용할 경우 내부적으로 cudaStreamSynchronize가 강제로 호출되는 문제가 있었습니다. 이로 인해 진정한 의미의 비동기 실행이 불가능했습니다. 이번 글에서는 이 문제를 해결한 microsoft/onnxruntime의 최근 변경 사항을 분석해 보겠습니다.
코드 분석: 동기화 플래그의 전파
이번 PR의 핵심은 ReplayGraph 가상 함수에 sync 파라미터를 추가하고, 이를 실행 옵션(RunOptions)에 따라 제어할 수 있도록 전체 호출 체인을 수정한 것입니다.
1. IExecutionProvider 인터페이스 변경
가장 먼저 모든 실행 제공자(EP)의 기반이 되는 인터페이스가 변경되었습니다.
Before:
virtual common::Status ReplayGraph(int /*graph_annotation_id*/) {
return Status::OK();
}
After:
/**
Run the instantiated graph.
@param sync If true, synchronize the device/stream after replay to ensure completion before returning.
If false, the caller is responsible for synchronization.
*/
virtual common::Status ReplayGraph(int /*graph_annotation_id*/, bool /*sync*/ = true) {
return Status::OK();
}
기존에는 Replay 시 동기화 여부를 선택할 수 없었으나, 이제 sync 플래그를 통해 호출자가 동기화 시점을 결정할 수 있게 되었습니다.
2. CUDAExecutionProvider의 구현
CUDA EP에서는 이 플래그를 실제 CUDA Graph Replay 로직까지 전달합니다.
Before:
Status CUDAExecutionProvider::ReplayGraph(int graph_annotation_id) {
return GetPerThreadContext().ReplayGraph(graph_annotation_id);
}
After:
Status CUDAExecutionProvider::ReplayGraph(int graph_annotation_id, bool sync) {
return GetPerThreadContext().ReplayGraph(graph_annotation_id, sync);
}
이 변경 사항은 PerThreadContext를 거쳐 최종적으로 CUDAGraphManager::Replay까지 전달됩니다. sync=false일 경우, 내부적으로 호출되던 cudaStreamSynchronize를 건너뛰게 됩니다.
3. 타 EP(TensorRT, DML 등)의 대응
재미있는 점은 CUDA 외의 다른 EP들의 처리 방식입니다. 리뷰어 hariharans29는 "동기화를 지원하지 않는 다른 EP들이 이 플래그를 무시하는 것이 위험하지 않느냐"는 질문을 던졌습니다.
이에 대해 메인테이너 tianleiwu는 다음과 같이 답변하며 코드를 보완했습니다.
- TensorRT/DML/JS/WebGPU: 이 EP들은 구조적으로 항상 동기적으로 Replay를 수행합니다.
- 따라서
sync=false요청이 들어오더라도 동기적으로 동작하는 것은 보수적(Conservative)인 선택이며, 안전합니다. (반대로 동기화가 필요한데 건너뛰는 것이 위험한 상황입니다.)
// onnxruntime/core/providers/tensorrt/tensorrt_execution_provider.cc
Status TensorrtExecutionProvider::ReplayGraph(int, bool /*sync*/) {
// The sync parameter is ignored: TRT EP always replays synchronously under a lock_guard in compute_func().
ORT_ENFORCE(IsGraphCaptured(0));
// ...
}
왜 이게 좋은 최적화인가?
1. 호스트-디바이스 병렬성 극대화
기존에는 Run() 함수가 반환되기 전 반드시 GPU 연산이 끝나기를 기다려야 했습니다. 이 최적화를 통해 CPU는 GPU가 커널을 실행하는 동안 즉시 제어권을 돌려받아 다음 배치를 준비하거나 다른 비즈니스 로직을 수행할 수 있습니다.
2. 성능 수치로 증명된 결과
PR 작성자가 H100 GPU에서 nsys 프로파일링을 통해 검증한 결과는 놀랍습니다.
| Config | Host-sync APIs inside Run() |
Result |
|---|---|---|
sync=on (기존) |
100회 (cudaStreamSynchronize) |
동기화 발생 |
sync=off (개선) |
0회 | 완전 비동기 달성 |
평균 2.3µs 정도 소요되던 동기화 오버헤드가 완전히 사라졌으며, 이는 초당 수천 번의 추론이 발생하는 고성능 서버에서 유의미한 처리량(Throughput) 향상으로 이어집니다.
일반적인 교훈: "Async by Default"의 어려움
프레임워크 설계에서 비동기 옵션을 제공하더라도, 내부 깊숙한 곳에 하드코딩된 동기화 지점이 하나라도 남아있다면 그 옵션은 무용지물이 됩니다. 이번 개선은 다음과 같은 교훈을 줍니다.
- 추상화 계층의 일관성: 인터페이스(
IExecutionProvider) 수준에서부터 옵션이 고려되어야 최하단 구현체까지 올바르게 전달될 수 있습니다. - 프로파일링의 중요성: 단순히 코드를 고치는 것에 그치지 않고,
nsys와 같은 도구로 실제cudaStreamSynchronize호출 횟수가 0이 되었는지 확인하는 과정이 최적화의 완성도를 결정합니다.
마치며
이제 ONNX Runtime 사용자들은 CUDA Graph와 IO Binding을 조합할 때, RunOptions에 단 한 줄의 설정을 추가함으로써 진정한 비동기 파이프라인을 구축할 수 있게 되었습니다.
run_options = ort.RunOptions()
run_options.add_run_config_entry("disable_synchronize_execution_providers", "1")
session.run_with_iobinding(io_binding, run_options)
# 이제 여기서 CPU는 멈추지 않고 즉시 다음 작업을 수행합니다!
저지연 추론 시스템을 설계하고 있다면, 이번에 업데이트된 ORT의 비동기 기능을 적극적으로 활용해 보시기 바랍니다.
참고 자료
- https://onnxruntime.ai/docs/performance/tune-performance/iobinding.html
- https://developer.nvidia.com/blog/cuda-graphs/
- https://onnxruntime.ai/docs/api/c/struct_ort_run_options.html
⚠️ 알림: 이 분석은 AI가 실제 코드 diff를 기반으로 작성했습니다.
관련 포스트
- [sglang] [SGLang] Blackwell(B200)에서 Diffusion Attention 성능을 7배 끌어올리는 Triton 커널 최적화 분석
- [onnxruntime] ONNX Runtime CUTLASS FMHA: BiasLoader 정렬 문제 해결로 안정성 및 호환성 향상
- [triton] Triton Reduce 커널 성능 최적화: Subtiling과 RowIdxs 도입
- [sglang] SGLang 성능 최적화: PDL 도입과 안전한 CUDA 동기화로 DSV3.2/GLM-5 가속하기
- [flashinfer] FlashInfer의 고성능 분산 연산: All-Gather Matmul 최적화 분석
PR Analysis 의 다른글
- 이전글 [sglang] SGLang NIXL HiCache 리팩토링 및 O_DIRECT 지원 추가: 성능 향상과 안정성 강화
- 현재글 : [onnxruntime] ONNX Runtime CUDA Graph: 진정한 비동기 추론을 위한 동기화 지점 제거
- 다음글 [sglang] SGLang의 NIXL 통신 최적화: Prep+Make API 도입을 통한 KV 캐시 전송 성능 향상
댓글