본문으로 건너뛰기

[ultralytics] Ultralytics 코드베이스 경량화: SciPy 의존성 감소 및 NumPy 기반 최적화

PR 링크: ultralytics/ultralytics#24572 상태: Merged | 변경: +31 / -14

들어가며

최신 딥러닝 모델을 개발하고 배포하는 과정에서 코드베이스의 효율성과 이식성은 매우 중요합니다. 특히, 외부 라이브러리에 대한 의존성을 최소화하는 것은 배포 환경의 복잡성을 줄이고 잠재적인 성능 병목 현상을 완화하는 데 도움이 됩니다. Ultralytics의 최근 PR은 이러한 목표를 달성하기 위해 SciPy 라이브러리에 대한 의존성을 줄이고, NumPy를 활용하여 핵심 연산의 성능을 최적화하는 데 중점을 두었습니다.

이 PR은 주로 다음과 같은 문제들을 해결합니다:

  1. 불필요한 SciPy 의존성 제거: 트래킹 및 플로팅 유틸리티에서 SciPy의 특정 함수들이 NumPy로 대체될 수 있음을 확인하고, 이를 통해 라이브러리 의존성을 줄였습니다.
  2. 성능 최적화: 특히 임베딩 거리 계산과 같은 반복적인 연산에서 NumPy 기반 구현이 SciPy보다 더 나은 성능을 보임을 확인하고 적용했습니다.
  3. 코드 가독성 및 유지보수성 향상: SciPy 임포트를 로컬 스코프로 제한하고, 새로운 내부 함수를 문서화하여 코드베이스의 명확성을 높였습니다.
  4. 사용자 경험 개선: 검증 시각화 옵션을 추가하여 사용자가 더 깔끔하고 제어된 출력을 얻을 수 있도록 했습니다.

이 글에서는 해당 PR의 주요 코드 변경 사항을 분석하고, 각 변경이 왜 좋은 최적화인지, 그리고 어떤 기술적 교훈을 얻을 수 있는지 자세히 살펴보겠습니다.

코드 분석

1. 트래킹 유틸리티: SciPy에서 NumPy로의 전환

이 PR의 핵심적인 변경 중 하나는 ultralytics/trackers/utils/kalman_filter.pyultralytics/trackers/utils/matching.py 파일에서 SciPy의 선형 대수 및 거리 계산 함수를 NumPy의 동등 함수로 대체한 것입니다.

1.1. kalman_filter.py: scipy.linalg.cho_factor/cho_solve 대체

Kalman 필터의 update 메서드에서 칼만 게인(Kalman gain)을 계산하는 부분은 scipy.linalg.cho_factorscipy.linalg.cho_solve를 사용하여 투영된 공분산 행렬(projected_cov)을 분해하고 해를 구했습니다. 이 PR에서는 이를 NumPy의 np.linalg.solve로 대체했습니다.

Before:

chol_factor, lower = scipy.linalg.cho_factor(projected_cov, lower=True, check_finite=False)
kalman_gain = scipy.linalg.cho_solve(
    (chol_factor, lower), np.dot(covariance, self._update_mat.T).T, check_finite=False
).T

After:

kalman_gain = np.linalg.solve(projected_cov, np.dot(covariance, self._update_mat.T).T).T

또한, gating_distance 함수에서 Mahalanobis 거리를 계산할 때 사용되던 scipy.linalg.solve_triangularnp.linalg.solve로 대체되었습니다.

Before:

cholesky_factor = np.linalg.cholesky(covariance)
z = scipy.linalg.solve_triangular(cholesky_factor, d.T, lower=True, check_finite=False, overwrite_b=True)

After:

cholesky_factor = np.linalg.cholesky(covariance)
z = np.linalg.solve(cholesky_factor, d.T)

왜 이게 좋은가?

리뷰 댓글에서 제공된 프로파일링 결과에 따르면, np.linalg.solvescipy.linalg.cho_factor/cho_solve 조합이나 scipy.linalg.solve_triangular보다 훨씬 빠릅니다. 특히 solve_triangular의 경우 NumPy 구현이 약 19배 이상 빠르다는 결과도 있습니다. 이는 NumPy가 최적화된 BLAS/LAPACK 라이브러리를 활용하여 이러한 선형 대수 연산을 매우 효율적으로 처리하기 때문입니다. SciPy의 cho_factor/cho_solve는 Cholesky 분해를 사용하지만, np.linalg.solve는 일반적인 선형 시스템 해결 알고리즘을 사용하며, 특정 조건에서는 더 효율적일 수 있습니다. 또한, SciPy의 특정 함수들은 내부적으로 추가적인 오버헤드를 가질 수 있습니다. 이러한 변경은 트래킹 모듈의 성능을 직접적으로 향상시킵니다.

1.2. matching.py: scipy.spatial.distance.cdist 대체 (코사인 거리)

객체 추적에서 트랙과 탐지 간의 유사성을 측정하는 데 사용되는 embedding_distance 함수는 특징 벡터(feature vector) 간의 거리를 계산합니다. 기존 코드에서는 scipy.spatial.distance.cdist를 사용하여 코사인 거리를 계산했습니다. 이 PR에서는 코사인 거리 계산을 NumPy 연산으로 직접 구현하여 scipy.spatial.distance.cdist 사용을 제거했습니다.

Before (주석 처리된 부분):

# cost_matrix[i, :] = np.maximum(0.0, cdist(track.smooth_feat.reshape(1,-1), det_features, metric))

After (코사인 거리 계산 부분):

if metric == 

## 참고 자료
- https://numpy.org/doc/stable/reference/generated/numpy.linalg.solve.html
- https://numpy.org/doc/stable/reference/generated/numpy.linalg.norm.html
- https://numpy.org/doc/stable/reference/generated/numpy.dot.html
- https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.distance.cdist.html
- https://numpy.org/doc/stable/reference/generated/numpy.convolve.html
- https://pytorch.org/docs/stable/generated/torch.compile.html

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

댓글

관련 포스트

PR Analysis 의 다른글