본문으로 건너뛰기

[ultralytics] Ultralytics YOLOv10 TensorRT 엔진 성능 최적화: FP16 및 INT8 속도 향상 비결

PR 링크: ultralytics/ultralytics#26223 상태: Merged | 변경: +39 / -11

들어가며

딥러닝 모델의 추론 속도는 실시간 애플리케이션의 성능을 좌우하는 핵심 요소입니다. 특히 객체 탐지 모델의 경우, 더 빠른 추론 속도는 더 높은 프레임률(FPS)을 의미하며, 이는 자율 주행, 로보틱스, 실시간 영상 분석 등 다양한 분야에서 필수적입니다. Ultralytics의 YOLOv10 모델은 강력한 성능을 자랑하지만, TensorRT와 같은 최적화된 추론 엔진을 사용할 때 그 잠재력을 최대한 발휘할 수 있습니다.

최근 Ultralytics YOLOv10 레포지토리에서는 TensorRT 엔진의 FP16 및 INT8 추론 속도를 최대 20%까지 향상시키는 PR이 병합되었습니다. 이 PR은 세 가지 주요 코드 변경을 통해 TensorRT 엔진의 성능을 개선했습니다. 본 글에서는 이 PR의 변경 사항을 상세히 분석하고, 각 최적화가 왜 효과적인지, 그리고 이를 통해 얻을 수 있는 일반적인 교훈은 무엇인지 살펴보겠습니다.

코드 분석

이번 PR의 핵심 변경 사항은 ultralytics/nn/backends/tensorrt.pyultralytics/utils/export/engine.py 파일에 집중되어 있습니다. 주요 변경 내용은 다음과 같습니다.

1. FP16 AutoCast 보정 (Calibration) 개선

TensorRT 11 버전부터는 모델의 데이터 타입을 명시적으로 지정해야 합니다. modelopt_quantize_onnx 함수는 ONNX 모델을 FP16으로 변환할 때 ModelOpt AutoCast를 사용하여 특정 연산이 FP16의 동적 범위(activation range)를 초과할 경우 FP32로 유지하도록 합니다. 이전에는 이 보정(calibration) 과정에서 torch.randn과 같은 무작위 노이즈 데이터를 사용했습니다.

하지만 무작위 노이즈는 실제 이미지 데이터보다 훨씬 큰 활성화 값을 생성하여, 모델의 초기 컨볼루션 레이어들이 불필요하게 FP32로 유지되는 문제를 야기했습니다. 예를 들어, yolo26s, yolo26m, yolo26x 모델에서 첫 세 개의 컨볼루션 레이어가 FP32로 실행되는 경우가 발생했습니다.

PR에서는 이 문제를 해결하기 위해 실제 이미지(ASSETS / "bus.jpg")를 사용하여 AutoCast를 보정하도록 변경했습니다. 실제 이미지를 사용하면 활성화 값이 훨씬 현실적으로 측정되어, FP16으로 충분히 처리 가능한 연산들이 FP32로 유지되는 것을 방지하고 FP16 활용도를 높입니다.

Before (Calibration with torch.randn):

calibration_data={input_name: torch.randn(*shape).cpu().numpy()}

After (Calibration with real image):

im = cv2.resize(imread(ASSETS / "bus.jpg"), shape[:1:-1])[..., ::-1].transpose(2, 0, 1)  # BGR HWC to RGB CHW
im = np.resize(im, shape[1:])  # repeat or drop channels for models that are not 3-channel
im = np.broadcast_to(im, shape).astype(np.float32, order="C") / 255
# ...
calibration_data={input_name: im}

PR 설명에 따르면, 실제 이미지로 보정했을 때 yolo26m 모델의 /model.0/conv/Conv 레이어에서 최대 절대 활성화 값이 torch.randn 사용 시 2599에서 122로 크게 감소했습니다. 이는 해당 레이어가 FP16으로 성공적으로 변환될 가능성이 높아졌음을 의미합니다.

2. SiLU 활성화 함수 융합 (Fusion)

TensorRT는 기본적으로 SiLU(Sigmoid Linear Unit) 활성화 함수를 직접 지원하지 않습니다. 따라서 sigmoid(x) * x 형태의 SiLU 연산은 컨볼루션 연산 이후 별도의 메모리 바운드 커널로 실행되어야 했습니다. 이는 특히 FP16 엔진에서 yolo26m 모델의 경우 전체 FP16 엔진 시간의 약 20%를 차지하는 병목 현상이었습니다.

이 PR에서는 SiLU를 TensorRT가 지원하는 tanh 기반의 동등한 형태로 변환하여 컨볼루션 연산의 에필로그(epilogue)에 융합했습니다. 변환된 형태는 x * (0.5 * tanh(0.5 * x) + 0.5) 입니다.

Before (Separate SiLU kernel):

# ... convolution ...
output = sigmoid(x) * x # Separate SiLU kernel

After (Fused SiLU using tanh):

# ... convolution ...
output = x * (0.5 * torch.tanh(0.5 * x) + 0.5) # Fused SiLU

하지만 모든 SiLU 연산을 융합할 수는 없었습니다. 특정 조건(예: 잔차 연결(shortcut)이 있는 경우)에서는 TensorRT가 융합된 컨볼루션, 활성화, 잔차 덧셈을 잘못 컴파일하는 문제가 발생했습니다. 이 문제는 NVIDIA/TensorRT#4854 이슈로 보고되었으며, yolo26m 모델에서 mAP50 점수를 0.701에서 0.458로 크게 하락시키는 심각한 성능 저하를 야기했습니다.

이를 해결하기 위해, PR에서는 이러한 문제가 발생하는 115개의 활성화 함수 중 15개에 대해서는 SiLU 융합을 제외하고 원래대로 별도 커널로 실행하도록 했습니다. 이 제외 조치는 성능에 거의 영향을 미치지 않으면서도 정확도를 유지합니다.

3. CUDA Graph를 이용한 엔진 재실행 (Replay)

이전에는 TensorRTBackend가 매 호출마다 모든 커널을 개별적으로 제출했습니다. 이는 매번 약 40마이크로초(µs)의 고정된 런치 오버헤드를 발생시켰습니다.

PR에서는 TensorRT 10 이상 버전에서 지원하는 CUDA Graph 기능을 활용하여 이 오버헤드를 제거했습니다. load_model 시점에 엔진을 한 번 캡처하여 CUDA Graph로 저장하고, 추론 시에는 이 캡처된 그래프를 재실행(replay)합니다. 이 방식은 매 호출마다 발생하는 고정된 런치 작업 시간을 제거하여 약 40µs의 지연 시간을 단축시킵니다.

Before (execute_v2):

self.context.execute_v2([binding.data.data_ptr() for binding in self.bindings.values()])

After (CUDA Graph replay):

self.bindings["images"].data.copy_(im)  # the capture reads this address, so the input must land in it
self.graph.replay()

CUDA Graph 캡처는 고정된 메모리 주소를 사용하므로, 입력 데이터는 포인터 바인딩 대신 캡처된 바인딩 버퍼로 복사됩니다. 이 기능은 동적 엔진(dynamic engines), DLA(Deep Learning Accelerator) 엔진, 또는 NMS(Non-Maximum Suppression) 연산이 포함된 엔진에는 적용되지 않습니다. 이러한 엔진들은 여전히 execute_v2를 사용합니다. CUDA Graph 캡처는 엔진 로드 시 약 6~8ms의 추가 시간과 68MB의 디바이스 메모리를 소모합니다.

왜 이게 좋은가?

이 PR의 변경 사항들은 TensorRT 엔진의 성능을 여러 측면에서 크게 향상시켰습니다.

성능 향상 수치

PR에서 제공된 벤치마크 결과는 이러한 최적화의 효과를 명확하게 보여줍니다. RTX PRO 6000 Blackwell GPU에서 측정된 결과에 따르면, 다양한 YOLOv26 모델과 배치 크기에서 상당한 지연 시간 감소를 확인할 수 있습니다.

  • FP16 엔진:
    • yolo26n (batch 1): -15.9%
    • yolo26m (batch 8): -18.5%
    • yolo26s (batch 8): -14.9%
  • INT8 엔진:
    • yolo26s (batch 1): -18% (CUDA Graph 적용 시)

특히, FP16 AutoCast 보정 및 SiLU 융합은 그래프 크기가 크고 FP16 연산이 많은 대형 모델 및 배치 크기에서 더 큰 효과를 보였습니다. CUDA Graph는 고정된 런치 오버헤드를 제거하므로, 호출 시간이 짧은 경우(예: 배치 1) 상대적으로 더 큰 백분율 향상을 보였습니다.

RT-DETR 모델의 경우, ReLU 백본이 AutoCast 임계값을 넘지 않고, 최적화된 활성화 함수들이 런타임에 큰 영향을 미치지 않는 레이어 외부에 위치하여 첫 두 가지 최적화의 효과가 미미했습니다. 하지만 CUDA Graph는 모델 종류와 무관하게 고정된 런치 오버헤드를 제거하므로, 모든 모델에서 일정한 성능 향상을 가져왔습니다.

정확도 유지

성능 향상과 더불어 중요한 것은 정확도 저하가 없다는 점입니다. PR에서는 PyTorch 모델과 FP16 엔진 간의 정확도 비교를 수행했으며, COCO val 데이터셋 및 각 모델의 태스크별 데이터셋에서 거의 차이가 없는 결과를 보여주었습니다. CUDA Graph 재실행 또한 execute_v2와 비트 단위로 동일한 결과를 보장했습니다.

일반적인 교훈

  1. 실제 데이터 기반 보정의 중요성: 모델 최적화 시 torch.randn과 같은 인위적인 데이터 대신 실제 운영 환경에서 사용될 데이터를 사용하여 보정하는 것이 훨씬 효과적입니다. 이는 모델의 실제 동작 특성을 더 잘 반영하여 불필요한 FP32 연산을 줄이고 FP16/INT8 활용도를 극대화할 수 있습니다.
  2. 연산 융합(Fusion)의 힘: 하드웨어 가속기(TensorRT)가 지원하는 연산 융합은 메모리 접근 및 커널 실행 오버헤드를 줄여 성능을 크게 향상시킬 수 있습니다. 다만, 융합 시 발생할 수 있는 잠재적인 정확도 저하나 컴파일 오류에 대한 주의와 예외 처리가 필요합니다.
  3. CUDA Graph 활용: GPU 커널 런치 오버헤드는 특히 짧은 추론 시간에서 전체 성능에 상당한 영향을 미칠 수 있습니다. TensorRT 10 이상에서 지원하는 CUDA Graph를 활용하면 이러한 고정 오버헤드를 제거하여 추론 속도를 크게 향상시킬 수 있습니다. 단, 동적 입력 크기, DLA, NMS 등 특정 조건에서는 사용이 제한될 수 있습니다.
  4. 점진적 최적화 및 검증: PR은 두 가지 주요 최적화(보정, 융합)와 CUDA Graph 적용을 단계적으로 검증하고, 각 단계별 성능 향상을 측정했습니다. 또한, 정확도 저하를 방지하기 위해 다양한 테스트 케이스와 실제 모델을 사용하여 철저히 검증했습니다.

리뷰 댓글 분석

리뷰 과정에서 여러 차례의 수정과 검증이 이루어졌습니다. 초기에는 SiLU tanh 재작성 및 관련 테스트가 포함되었으나, NVIDIA/TensorRT#4854 이슈와 연관된 복잡성 및 INT8에서의 효과 미미함 때문에 최종적으로 제거되었습니다. 이는 성능 향상 대비 코드 복잡성과 유지보수 비용을 고려한 합리적인 결정이었습니다.

특히, test_export_engine_fp16_parity 테스트는 원래 SiLU 재작성 시 발생할 수 있는 miscompile을 잡기 위한 목적이었으나, 해당 miscompile이 박스 채널이 아닌 클래스 점수에 영향을 미치지 않아 테스트의 유효성이 떨어졌습니다. 따라서 이 테스트는 제거되었습니다.

또한, CUDA Graph 캡처 시 장치(device) 컨텍스트 관리의 중요성이 강조되었습니다. deserialize_cuda_enginecreate_execution_context 함수가 현재 CUDA 장치에 바인딩되므로, 엔진 로드 시 올바른 장치 컨텍스트를 설정하는 것이 중요함이 확인되었습니다. 이로 인해 torch.cuda.device(self.device) 컨텍스트가 추가되었습니다.

마지막으로, CUDA Graph 캡처 실패 시 예외 처리가 강화되어, 실패 시 execute_v2 경로로 안전하게 전환되도록 수정되었습니다.

References

참고 자료

⚠️ 알림: 이 분석은 AI가 실제 코드 diff를 기반으로 작성했습니다.

댓글

관련 포스트

PR Analysis 의 다른글