오랜만에 글을 써 봅니다. 근래에 학업 공부를 하고 그러다 보니까 블로그에 공부한 걸 잘 안 올렸네요.(나름 자료를 찾아보고 틈틈히 보긴 했는데 쩝)


  2018년 겨울방학에 들어서고, 꽤 전부터 생각해오던 CUDA를 이용한 프렉탈 그리기를 직접 프로그래밍했습니다. 중학교 때 프렉탈을 처음 알게 되고 나서, 그 신비함과 아름다움에 빠져서 언젠가는 꼭 프렉탈을 그려내는 프로그래밍을 하겠다고 다짐을 했는데(그 옛날에 julia set의 상수마다 이미지를 만들어서 1TB 하드를 꽉 채우겠다는 생각도 해본 거 같습니다. 뭐 정작 여러 가지 그려보다 보니 이쁜 모양이 나오는 julia set 상수는 그렇게 많진 않았지만요)겨울방학 들어서고 하게 되었네요.


  요즘엔 안드로이드 프로그래밍을 거의 안 하고(앱 유지보수 정도...) 딥 러닝 등을 Python으로 하고 있습니다. 그래서 이번 프렉탈 그리기는 제가 옛날부터 주로 쓰던 Java AWT라던가 그런 걸로 만들지 않았고, Python 3에다가 PyQt5와 PyCUDA를 활용해서 만들어 봤습니다. 아래 이미지들이 제가 지금 제작한(그리고 제작하고 있는) 앱의 스크린샷입니다.

 

(GitHub 링크: https://github.com/AMakeApp/FractalViewer)


  이러한 프렉탈을 그리는데는 사실 CPU를 써도 크게 상관은 없습니다. 단지 GPU를 쓰는 이유는 프렉탈을 그리는데 들어가는 알고리즘이 상당한 연산을 필요로 하기 때문입니다. 이 연산 과정을 알려면 위의 프렉탈들이 어떻게 만들어지는지 알아볼 필요가 있습니다.


  제가 현재 앱에 구현해 둔 프렉탈은 위 두 이미지인 Mandelbrot Set과 Julia Set입니다. 사실 이 둘은 모양이 상이함에도 상당한 상관관계가 있습니다. 왜냐하면 이 둘의 집합을 계산하는 과정이 상당히 유사하기 때문이죠. 그렇기에 좀 더 많이 알려져 있는 Mandelbrot Set에 대해 알아봅시다. Mandelbrot Set은 이름에서 보이듯이 Mandelbrot가 고안한 집합입니다. 사실 많이 알려져 있듯이 저렇게 알록달록 해야지 Mandelbrot Set은 아니고 수학적으로는 아래 이미지처럼 복소평면 상에서 검정색 점들의 집합이 Mandelbrot Set입니다.

Mandelset hires


이 집합은 일반적으로 우리가 집합을 정의할 때와는 조금 다르게 특이하게 정의되어 있는데, 다음 점화식에서 수열이 발산하지 않게 하는 복소수 $c$의 집합으로 정의되어 있습니다.

$$z_0 = 0, z_{n + 1} = z_n^2 + c$$


즉, 이 집합은 무한 집합일테고 복소평면 상의 어느 한 점에 대해 이 점이 Mandelbrot Set에 속하는지 계산하려면 각 복소수에 대해 수많은 반복 연산을 통해 수열의 발산 여부를 확인해야 합니다. 다행히도 $| z_n | > 4$라면 발산한다고 볼 수 있기 때문에 일정한 반복 연산 후 $z_n$의 절대값을 비교하면 좀 더 수월하게 계산 할 수 있습니다.(아마 이 부분이 수학적으로 증명되어 있는 거 같습니다. 이 문서를 참고하세요) 하지만 그렇다고 해도 수많은 반복 연산이 필요한 것은 사실입니다. 초중고등학교에서 배우는 것처럼 단순히 함수꼴로 구할 수 있는 것도 아니고, 각 점에 대해 일일히 노가다를 해야하는 것과 마찬가지니까요. 각 점에 대해서는 제가 대충 해본 결과 200번 정도 이상하면 눈으로 보기에는 프렉탈이 충분히 이쁘게 나옵니다. 커다란 프렉탈 이미지를 생성하려고 2000 x 2000의 프렉탈 이미지를 생성한다고 합시다. 대략 이정도면 200 * 2000 * 2000 = 8억 번 정도의 연산이 필요로 합니다. 일반적으로 한 알고리즘에 대해서 1억 번 연산에 대한 소요시간을 1초로 잡을텐데(요즘 CPU는 더 빨라서 더 많이 연산을 할 수는 있습니다) 프렉탈 이미지를 계산하기 위해서는 8초나 걸린다는 겁니다. 요즘 CPU는 중사양 CPU도 4코어 8쓰레드 정도 가질텐데, 멀티 코어 프로그래밍을 한다고 해도 최소 1초는 걸린다는 이야기가 됩니다.


  이렇게 CPU는 프렉탈 이미지를 계산하는 데는 상당히 오래 걸리기 때문에 GPU의 수많은 코어를 사용해서 더 빠르게 계산을 할 수 있습니다. 비유를 하자면 CPU는 고급 인력 4~8명이 일하는 것이라면 GPU는 초보자 수 천 명이 일을 하는 것과 비슷합니다. 프렉탈 연산이 많은 반복 연산이 필요로 하지만 정작 그 연산 자체가 그렇게 복잡하지 않기 때문에(특히 분기 구문 같은 것이 많지 않기 때문에) 이러한 경우에는 GPU에서 빠르게 계산할 수 있습니다. 이러한 이유로 프렉탈을 빠르게 그려내기 위해서 GPU를 활용했습니다.


  GPU로 그려내기로 결정하고 나서, 제 컴퓨터에 장착되어 있는 GTX970에서 GPGPU 프로그래밍을 하기 위해서 Nvidia에서 지원하는 CUDA를 활용했습니다. 여기서 CUDA는 일반적으로 CUDA C를 사용해서 C/C++과 함께 사용되는데, GUI로 이미지를 보여주면서 편하게 프로그래밍하고 싶었기 때문에 Python 3를 기반으로 CUDA를 사용할 수 있게 만들어 둔 PyCUDA를 사용했습니다. 그리고 유려한 GUI를 보여주는 Qt를 Python에 접목시킨 PyQt5를 사용했고요.


다음 글부터는 이 Fractal을 그리는데 어떻게 CUDA 프로그래밍을 했는지 제 시행착오와 함께 CUDA에 대해 이야기해보겠습니다.

제가 IDC에 서버를 두고 앱 서버를 돌리는데, 이번에 남는 서버가 한 대 생겨서(문제는 램 드라이버 문제 때문에 CentOS 6.4부터 안 올라감 ㅁㅊ) 거기에 GPU 하나 끼워서 제 연구서버로 써볼까 생각을 해봤습니다. 서버가 1U짜리이기 때문에 서버 케이스 안에 GPU를 넣는 건 좀 오바고, riser(PCIE 연장선)을 이용해서 좁디 좁은 케이스 밖에 GPU를 두려고 생각을 해봤습니다. 찾아보니까 PCIE riser가 있긴 있는데, 대부분 비트코인 같은 사이버코인 마이닝용으로 나온 PCIE x16 to x1 밖에 없더군요(;;) 나름 서버 내부에 x16 레인이 있기 때문에 써보고 싶었는데 x1과 x16의 GPGPU 상의 성능이 크게 차이가 나지 않는다는 소리가 들어서 한 번 테스트해봤습니다.


만저 제 컴퓨터 사양은 다음과 같습니다.

CPU: Intel i7-5820K

RAM: DDR4 16GB

GPU: GTX970

HDD: SATA2 Seagate 320GB (08년도쯤에 생산된 것으로 추정됩니다 ㄷㄷ)

PSU: SuperFlower SF-600R14SE Silver Green FX


OS: Ubuntu 16.04.1

Kernel: Linux 4.4.0-34-generic

Software: neural-style, Torch7, CUDA 7.5, cuDNN(버전은 기억이 안 나네요)


참고로 테스트시에는 모니터 3개를 켜뒀고(DVI x 2, DP x 1) 크롬 켜두고, 터미널 창 세 개 켜뒀습니다. 한 터미널은 공백 상태(log 저장용이라 걍 켜둔 셈), 한 터미널에서는 neural-style이 돌아가고 있었고, 나머지 한 터미널에서는 아래의 명령어로 GPU를 모니터링했습니다.


watch -n 1 nvidia-smi


* 테스트는 riser를 끼운 상태(x1)에서 오후 12시쯤에 테스트했고, riser를 안 끼운 상태(x16)에서는 오후 8시쯤에 테스트했습니다. nvidia-smi를 확인해보니 GPU에 쓰로틀링이 걸리던데, 일단 아래 두 테스트할 때 riser 끼운 상태에서는 GPU 사용률이 95%에서 맴돌았고 riser를 안 끼운 상태에서는 97~100%를 맴돌았습니다. 제 방은 에어컨따위 틀지 않고 오직 선풍기만 틀어서 방안 온도는 크게 변하지 않았을 것으로 추정되어 테스트에 큰 영향은 없을 것이라 생각이 됩니다.


비교를 위해 사용한 riser는 아래 링크에 있는 riser입니다. 사실 이더리움 마이닝 테스트용으로 한 개 샀던 것인데, 딱히 마이닝할 일도 없고 이번에 성능 비교해서 잘 나오면 IDC에 박으려고 생각했던 것입니다. (무려 그 일주일 전에 알리에서 샀던 이어폰보다 먼저 오시는 riser 클라스 ㄷㄷ)

http://www.aliexpress.com/item/PCIe-PCI-E-PCI-Express-Riser-Card-1x-to-16x-USB-3-0-Data-Cable-SATA/32548726067.html?spm=2114.13010608.0.50.CpdraB



라이저를 끼우고 그래픽카드를 외부로 뺀 상황입니다.


그리고 테스트를 돌렸습니다. 테스트는 위에 써둔 것처럼 Torch7 기반의 neural-style을 GPU로 돌려봤습니다. 명령어는 아래와 같습니다.


time th neural_style.lua -style_image examples/inputs/picasso_selfport1907.jpg -content_image examples/inputs/brad_pitt.jpg -gpu 0


총 3회를 테스트하였으며 맨 밑의 더보기를 통해 그 로그들을 확인하실 수 있습니다. 참고로 이상하게 컴퓨터를 부팅한 이후 바로 torch를 돌리면 준비 때문인지 약간 딜레이가 있어서 두 테스트 다 처음에 1회 간단히 돌린 이후에 테스트를 돌렸습니다. (즉, 간단히 돌린 이 1회를 제외한 그 다음 테스트부터 아래 표에 반영되어 있습니다.)


테스트 결과는 다음과 같습니다. time 명령어를 통해 real 시간을 측정하였습니다.



흠... 생각보다 정말 듣던대로 x16과 x1에서의 성능차이는 크지 않았습니다. x16 riser가 구해지지 않으면, 지금 가지고 있는 이걸 IDC에 있는 서버에 끼워서 연구용 서버로 사용해야할 거 같네요. 굿굿


ps. 차트 이쁘게 넣어보겠다고 찾아보다가, Google Chart 모듈을 찾아서 넣어봤습니다. 정작 글 쓰면서 차트 모듈 사용해서 차트 작성한게 1시간 반정도 먹는 듯... ㅠㅠ


neural-style w/ riser


neural-style w/o riser


'Artificial Intelligence' 카테고리의 다른 글

PCIE x16 VS PCIE x1에서의 GPU 성능 비교  (0) 2016.08.10

+ Recent posts

티스토리 툴바