[Utilities] Pillow-SIMD
Introduction
Pytorch를 이용해 이미지/비디오 데이터를 학습한다 했을 때, 데이터를 augment 하기 위해서는 보통 torchvision의 transform 모듈을 흔히 이용한다.
기본적으로 Pytorch의 torchvision 라이브러리는 PIL (Python Imaging Library) Image 모듈 혹은 torch Tensor 모듈을 이용해 이미지 데이터를 다루도록 짜여져 있기 때문에, 각종 학습에 필요한 image transformation을 정의할 때에는 PIL Image 모듈의 멤버 함수나, PIL Image를 인자로 받을 수 있는 함수들을 빈번하게 이용하게 된다.
아래는 torchvision.transform 모듈의 함수 예시로, PIL Image 혹은 torch Tensor를 인자로 받을 수 있도록 만들어진 것을 확인할 수 있다. 리턴 타입도 마찬가지이다).
하지만 가끔, 큰 스케일로 image transform이 수행되어야 할 때, PIL Image의 transformation의 연산 시간이 답답하게 느껴질 때가 있다. 본인은 특히 1024*512 이상의 크기를 가지는 이미지를 128*128 사이즈로 resize 시키는 과정에서 학습 시간이 크게 지연되는 것을 경험했다.
그렇다고 이미지를 미리 하나하나 resize 해서 저장해 두기도 애매한 상황이고, 그래서 이미지 전처리 과정을 좀 빠르게 수행시킬 수 있는 방법이 없나 해서 커뮤니티를 뒤져본 결과 발견한 것이 바로 Pillow-SIMD 라이브러리다.
글에서 뜬금없이 Pillow라는 단어가 등장했는데, 소개하고 넘어가자면 Pillow는 PIL 라이브러리에서 fork 된 라이브러리이다 (프로젝트가 갈라져 나왔다고 생각하면 된다. 참고). 기존 PIL 라이브러리에 비해서 더 다양한, 최적화된 기능을 제공하며, 보통 PIL Image를 이용한다 하면 이전의 PIL 라이브러리가 아닌 Pillow 라이브러리를 사용한다고 이해하면 된다. 일단 PIL의 마지막 stable release는 현재 기준으로 약 11년 반 전이다.....
앞서 torchvision 부분에서 언급한, PIL Image를 다루는 함수들 또한 Pillow 라이브러리의 함수들을 가져다 쓰는 것이 보통이다.
그럼 지금 소개하고자 하는 Pillow-SIMD는 뭘까? Pillow-SIMD 또한 Pillow 라이브러리에서 fork되어 나온 라이브러리다. 앞에 Pillow는 Pillow에서 갈라져 나왔으니 그렇다 쳐도 SIMD는 무엇을 뜻하는 걸까?
SIMD
SIMD는 Single instruction, multiple data의 약자이며 multiple process element를 사용해, 많은 수의 데이터 포인트에 대해서 동일한 연산을 병렬적으로 수행하는 것, 혹은 수행할 수 있는 컴퓨터를 지칭하는 단어이다.
위의 그림을 통해 개략적인 철학을 이해할 수 있는데, oridinary CPU는 네 번의 곱셈을 위해 메모리에서 값을 네 번 읽어 오고 네 번 연산을 수행해 다시 네 번 메모리에 쓰는 작업을 하고 있다면 SIMD CPU는 여러 값을 한 번에 읽어 와 병렬적으로 한 번 연산을 수행한 후, 다시 한 번에 네 값을 쓰는 작업을 하고 있다.
이런 작업 방식은 컴퓨터 비전 혹은 그래픽스에서 처럼 많은 수의 데이터 포인트를 한번에 빼거나 더하는 작업을 해야할 때 특히 유용하다 (밝기 변화를 표현할 때 등).
여기서 대략적인 촉이 오는데, Pillow-SIMD는 이 SIMD CPU를 이용해 데이터 처리 속도를 가속화하는 기능을 제공하고 있다. 한번 알아보도록 하자.
Pillow-SIMD
Pillow-SIMD는 기존의 Pillow 버전을 따라가고 있으며, 동일한 Pillow 버전을 같은 버전의 Pillow-SIMD로 완전히 대체해 사용하는 것이 가능하다. 먼저 설치 방법부터 보자.
Installation
$ pip uninstall -y pillow
$ CC="cc -mavx2" pip install -U --force-reinstall pillow-simd
위와 같이 기존에 깔려있던 Pillow 라이브러리를 제거하고 설치하면 된다. 이후엔, 기존의 Pillow 라이브러리를 사용하던 것과 같이 그대로 이용하면 된다. 임포트 이름을 바꿔줄 필요도 없다.
하지만 모든 기능들이 SIMD의 효과를 보는 것은 아닌데, SIMD를 이용해 가속화가 되어 있는 이미지 연산들의 목록은 아래 사진에 나와 있다. 앞서 언급했던 resize나, 커널이 클 때 꽤 골치 아파지는 Gaussian blur와 같은 연산이 포함되어 있다.
위 사진의 우측 항들에 적혀있는 SSE, AVX 등의 단어는 SIMD instruction set의 종류로, 프로세서 별로 사용 가능한 instruction set이 다르며 각 instruction set의 특성이 다르다는 것만 알아주면 좋겠다 (지원하는 integer나 floating-point의 비트 수가 다르다던지...).
이런 식으로, 이용할 수 있는 instruction set이 개별 하드웨어에 따라 다르며, 모든 CPU 아키텍쳐를 일일이 지원할 수는 없기 때문에 SIMD 이용 기능은 기존 Pillow 프로젝트에 병합되고 있지 않다. 하지만 웬만한 요즘 컴퓨터에서는 문제 없이 설치해 사용할 수 있다.
Performance Check
한번 간단한 테스트를 돌려 보고 마치도록 하자.
from PIL import Image
import numpy as np
from time import time
np_img = np.random.randint(0, 256, [4096, 4096, 3]).astype(np.uint8)
start = time()
for i in range(20):
pil_img = Image.fromarray(np_img)
pil_img.resize([128, 128])
end = time()
print(f"Elapsed time: {end-start}")
# Pillow
>> Elapsed time: 2.008594512939453
# Pillow-SIMD
>> Elapsed time: 0.8410162925720215
Pillow와 Pillow-SIMD를 쓰는 각각 다른 가상환경 상에서 같은 스크립트를 테스트 했을 때, 확연한 연산 시간 차이를 보여주고 있다. Pillow 라이브러리의 사이트에서는 Pillow-SIMD와 PIL 라이브러리를 포함한 여러 성능 벤치마킹 결과를 리포트 하고 있는데, 이것을 한번 참고해 보는 것도 좋을 것이다. UI가 사진으로 보기에는 적합하지 않아서, 아래에 링크를 첨부해 둔다.
아래는 참고할 만한 사이트들이다.
References
forking이란?: guides.github.com/activities/forking/
Pillow 도큐멘테이션: pillow.readthedocs.io/en/stable/
Pillow-SIMD 도큐멘테이션: github.com/uploadcare/pillow-simd
Pillow-SIMD 성능 벤치마크: python-pillow.org/pillow-perf/
torchvision 도큐멘테이션: pytorch.org/vision/stable/index.html