서론

취준생에서 현업 AI 엔지니어로 전향하면서, 실무에 필요한 다양한 기술들을 빠르게 습득하고 있는 요즘입니다. 특히, 회사의 리눅스 서버에 접속해 코드를 작성하고 협업하는 일은 일상이 되었는데요.
다행히 저는 입사 전부터 리눅스 커맨드라인과 Git을 어느 정도 다룰 줄 알았기에, 이런 기초적인 지식이 업무에 큰 도움이 되고 있습니다. 아주 간단해서 입사 후 빠르게 배울 수도 있는 것들이기도 하지만, 이런 기본적인 것들을 모르고 시작하면 제일 불편한 건 결국 나 자신이더라고요.
특히 AI 엔지니어로서 GPU를 다루는 일은 필수적입니다. 그래서 저 역시 여러 개의 GPU를 병렬처리 하는 방법, 메모리 배치와 사용량 등에 대해 항상 고민하려고 노력하고 있어요. 그러면서 자연스럽게 서버에 연결된 GPU 사용량과 메모리 상태를 실시간으로 추적하는 것이 필요해졌는데요. 다양한 툴을 비교해보며 저에게 맞는 최적의 도구를 찾아가고 있습니다.
오늘은 이처럼 GPU 모니터링을 더욱 쉽게 해줄 수 있는 다양한 명령어 중에서, 제가 가장 자주 사용하는 3가지를 알려드리고 비교해 보려고 합니다.
참고로 저는 VScode를 사용해 작업합니다!


1. nvidia-smi

GPU 모니터링의 근본, 바로 nvidia-smi입니다. 엔비디아가 제공하는 이 툴은 GPU 사용량, 메모리 상태, 온도 등을 보여주는데요. 저는 간단하게 GPU ID 확인이나 특정 프로세스를 kill할 때 주로 사용합니다. 가장 기본이지만 또 근본이라서 다른 화려한 툴이 많지만 동시에 함께 자주 사용하게 되는 그런 툴입니다 :)
 

nvidia-smi

간단한 명령어로 실행할 수 있습니다.

nvidia-smi 출력 예시 (1)

위 이미지에서는 nvidia-smi 명령어를 통해 출력된 GPU 상태 정보가 나와 있는데요.
각 GPU의 명칭, 온도, 메모리 사용량, 전력 소모량 등 주요 정보를 확인할 수 있고, 시스템에 설치된 모든 GPU의 상태를 한눈에 보여주는 특징이 있습니다. 아주 기본적인 GPU 모니터링 방법이지요.

nvidia-smi 출력 예시 (2)

 특히 nvidia-smi 출력 하단의 Processes 섹션에서 현재 GPU 메모리를 사용 중인 프로세스 목록, 그리고 각 프로세스의 PID(프로세스 ID)를 확인할 수 있습니다. 특정 프로세스가 과도하게 GPU 메모리를 사용하거나 문제가 발생한 경우, kill [PID] 명령어로 해당 프로세스를 종료하여 메모리를 해제할 수 있습니다.

# 실행
nvidia-smi
nvidia-smi -l 2 (2초마다 업데이트)
watch nvidia-smi (실시간 모니터링)

 

  • -l 옵션은 nvidia-smi 자체에서 제공하는 기능으로, 지정한 초 단위로 GPU 상태를 자동으로 반복해서 업데이트해줍니다.
  • watch 명령어는 리눅스 기본 명령어로, 특정 명령을 주기적으로 실행하여 그 출력을 갱신하는 기능을 합니다. nvidia-smi 뿐만 아니라 모든 명령에 사용할 수 있습니다.

2. nvitop

화려한 모니터링의 시작! nvitop입니다.

nvitop을 처음 접하면 "우와, 이게 가능해?"라는 생각이 들 만큼 시각적으로 무척 화려합니다. 저도 오로지 nvidia-smi만 알다가, 처음으로 nvitop을 설치하고 실행했을 때, 무척 놀랐던 기억이 있습니다.
nvitop을 사용하면 실시간으로 GPU 사용량을 모니터링할 수 있어 매우 편리합니다. 비슷한 툴로 nvtop도 있는데, 둘 다 nvidia-smi에서 보여주는 기본 정보를 보다 직관적으로 시각화합니다. 특히 많은 GPU를 동시에 모니터링할 때 빛을 발합니다.

pip install nvitop
conda install -c conda-forge nvitop

# 실행
nvitop

위와 같이 2가지 방법으로 설치, 간단하게 실행할 수 있습니다. 가상환경을 사용중이시라면 conda를 이용해서 설치하고 실행하시는 것을 추천드립니다.


3. gpustat

gpustat은 GPU 상태를 실시간으로 모니터링할 수 있는 Python 기반 도구입니다. GPU 사용량, 메모리 사용량, 온도 등을 명령어 한 번으로 간편하게 확인할 수 있습니다. nvidia-smi 명령어를 기반으로 GPU 정보를 수집하기 때문에 NVIDIA GPU에서만 사용 가능한 특징이 있습니다.
아래는 gpustat 깃허브 링크입니다.
https://github.com/wookayin/gpustat

GitHub - wookayin/gpustat: 📊 A simple command-line utility for querying and monitoring GPU status

📊 A simple command-line utility for querying and monitoring GPU status - wookayin/gpustat

github.com

pip install gpustat
conda install -c conda-forge gpustat

# 실행
gpustat
gpustat -i 
gpustat -i 2 (2초마다 업데이트)
gpustat -cp (CPU사용량까지 포함해서 업데이트)

다양한 옵션들

  • --color : Force colored output (even when stdout is not a tty)
  • --no-color : Suppress colored output
  • -u, --show-user : Display username of the process owner
  • -c, --show-cmd : Display the process name
  • -f, --show-full-cmd : Display full command and cpu stats of running process
  • -p, --show-pid : Display PID of the process
  • -F, --show-fan : Display GPU fan speed
  • -e, --show-codec : Display encoder and/or decoder utilization
  • -P, --show-power : Display GPU power usage and/or limit (draw or draw,limit)
  • -a, --show-all : Display all gpu properties above
  • --id : Target and query specific GPUs only with the specified indices (e.g. --id 0,1,2)
  • --no-processes : Do not display process information (user, memory) (#133)
  • --watch, -i, --interval : Run in watch mode (equivalent to watch gpustat) if given. Denotes interval between updates
    • -i 옵션은 인터벌(interval)을 설정하는 옵션으로, 주기적으로 GPU 상태를 업데이트를 해주기 때문에 실시간 모니터링을 매우 편리하게 할 수 있습니다!
  • --json : JSON Output (#10)
  • --print-completion (bash|zsh|tcsh) : Print a shell completion script. See #131 for usage.

마무리

여러 가지 화려한 툴들이 있지만, 저는 gpustat -i를 가장 자주 사용하고 있습니다. 한 눈에 들어오는 간단하고 깔끔한 인터페이스가 무척 편리하고, GPU를 멈추거나 죽일 때도 금방 빠르게 확인할 수 있어서 속이 시원하거든요. nvitop처럼 화려한 툴도 좋지만, 아직까지는 실용성과 간편함 면에서 gpustat이 제 마음 속 GPU 모니터링 툴 1위입니다.
물론, 정답은 없습니다. 취향에 따라, task에 따라 다양한 툴을 비교해보며 본인에게 맞는 최적의 도구를 선택해서 사용하시면 되겠습니다.
그럼 여러분도 GPU 모니터링, 이제 걱정 없이 화이팅 하세요! 👾 감사합니다 :)

CPU란 무엇인가

CPU는 Central Processing Unit의 약자로, '중앙 처리 장치'라고도 불립니다. 인간으로 따지면 두뇌에 해당하는 역할을 하는데요. 컴퓨터의 두뇌로서 모든 명령을 처리하고 중요한 계산을 하는 역할을 합니다. 사용자가 "1+2를 계산해라"라는 명령을 내렸을 때, 이 명령을 받아 계산을 실행하고 "3"이라는 결과값을 도출해 내는 게 CPU가 하는 일이죠.

저는 올해 초 맥북 프로를 구매했는데요. 칩(SoC: System on a Chip)으로는 M3 Pro를 골랐습니다. M3 Pro에는 12코어 CPU(6개의 고성능 코어, 6개의 고효율 코어)와 18코어 GPU가 포함되어 있습니다. 여기서 말하는 '12코어'란 CPU 내부에 12개의 처리 유닛(코어)가 있다는 뜻인데요. 쉽게 설명하자면, 컴퓨터가 여러 가지 작업을 동시에 할 때 이 코어들이 나눠서 일을 처리하는 것이죠. 각 코어는 독립적으로 작업을 처리할 수 있기 때문에, 코어가 많을수록 한 번에 더 많은 작업을 할 수 있어요. 예를 들어서, 제가 구매한 M3 Pro보다 비싼 모델인 M3 Max의 경우 16코어 CPU와 40코어 GPU가 탑재되어 있기 때문에 더욱 성능이 좋습니다.

이렇게 코어가 많을수록 CPU가 '병렬 처리'라는 것을 잘 해낼 수 있어서, 컴퓨터는 멀티태스킹이나 고사양 작업을 잘 해낼 수 있게 됩니다. 병렬 처리는 여러 작업을 독립적으로, 동시에 처리하는 능력을 의미하는데요. 병렬 처리를 활용하면 작업을 빠르게, 효율적으로 완수할 수 있어요. 

그렇지만 병렬 처리가 항상 100% 반드시 효율적인 것은 아닙니다. 작업을 병렬로 나누는 과정에서 오히려 시간이 더 소모될 수도 있고요. 소프트웨어가 병렬 처리를 지원하지 않으면 모든 코어를 제대로 활용하지 못할 수 있습니다. 따라서 CPU 자원을 잘 활용할 수 있도록 프로그램을 구성하는 것이 무척 중요하다고 볼 수 있겠습니다.


CPU와 GPU의 차이

CPU는 마치 스포츠카와 같습니다. 한두 사람을 아주 빠르게 운송할 수 있죠. 반면에 GPU는 고속버스와 같습니다. 속도는 스포츠카에 미치지 못하지만, 한 번에 훨씬 더 많은 사람을 태울 수 있어요. 만약 같은 단위 시간 동안 운송할 수 있는 사람의 수가 차량의 성능에 해당한다면, 고속버스에 해당하는 GPU의 성능이 더 좋다고 볼 수 있는 것이죠.

그러나 CPU와 GPU는 마치 스포츠카와 고속버스처럼 그 용도와 목적, 성질이 무척 다릅니다. GPU는 CPU보다 병렬 처리를 수십 배 잘해낼 수 있지만 하나의 작업을 빠르게 실행하는 속도는 느리고, CPU는 OS를 실행하는 데 필요한 여러 가지 기능을 다양하게 탑재하고 있는 등, 차이점이 많아요. 따라서 내가 실행하고자 하는 작업이 CPU와 GPU - 둘 중 무엇에 더 최적화 되어 있는지 잘 판단하고 알맞은 것을 선택하는 것이 중요합니다.


CPU 집약적 task

AI 엔지니어로서 머신 러닝, 딥러닝 모델을 많이 다뤄온 저에게는 항상 GPU에 대한 고민이 먼저 이루어지곤 했습니다. 딥러닝 모델은 대량의 데이터와 수십억 개의 파라미터를 다루고, 행렬 곱셈(Matrix Multification/Dot Product) 같은 대규모 수학 연산을 자주 하기 때문에, 많은 메모리 대역폭과 고성능 연산 능력을 가지고 있는 GPU의 병렬 처리가 필수적으로 요구됩니다. 그래서 GPU 자원이 턱없이 부족한 환경에서 공부를 해오던 저에게는 좋은 사양의 GPU에 대한 갈증이 항상 있어 왔습니다.

그런데 이번에 처음으로 CPU 집약적 task, CPU의 병렬 처리에 대한 고민을 하게 되었어요. 저는 현재 맡은 업무에서 빅데이터셋의 품질 관리를 위해 문서간의 '편집 유사도(Edit Similarity / Levenshtein Distance)'를 계산하는 알고리즘과 코드를 작성하고 있는데요. 전체 과정에서 논리적이고 순차적인 연산이 매우 중요하기 때문에,이는 GPU보다 CPU를 잘 활용해야 하는 task에 해당했습니다. 또한 해당 과제는 가지고 있는 데이터 내의 모든 문서쌍에 대해 편집 유사도를 계산해야 하는 O(n * m)의 시간 복잡도를 가진 작업으로, 엄청난 연산량이 요구되는 만큼 CPU의 병렬 처리가 반드시 필요한 상황이었습니다.


CPU 병렬 처리를 지원하는 라이브러리

파이썬에는 CPU의 병렬 처리를 지원하는 다양한 라이브러리가 존재합니다. 저는 그 중에서 아래와 같이 4개의 라이브러리를 선택, 직접 비교해 보았습니다.

[1] Multiprocessing

  • Python의 표준 라이브러리로 추가 설치 없이 사용할 수 있음
  • CPU의 여러 코어를 활용해 병렬 프로세싱을 쉽게 구현할 수 있음. 각 프로세스는 독립된 메모리 공간을 사용하기 때문에, 안전한 병렬 처리가 가능함. 단순한 병렬 작업을 수행할 때, 특히 CPU 집약적인 작업에 적합함
  • docs : https://docs.python.org/ko/3/library/multiprocessing.html
from multiprocessing import Pool

def f(x):
    return x*x

if __name__ == '__main__':
    with Pool(5) as p:
        print(p.map(f, [1, 2, 3]))
        
# [1, 4, 9]

[2] Ray

  • Ray는 기본적으로 CPU 자원을 병렬로 활용할 수 있도록 설계되어 있으며, 여러 CPU 코어를 이용해 동시에 여러 작업을 실행할 수 있게 해 줌.CPU, GPU 자원을 자동으로 관리하고 최적화된 성능을 제공.
  • 기존 코드를 수정할 필요 없이 @ray.remote 데코레이터를 사용해 매우 쉽게 병렬 작업을 처리할 수 있어 사용이 편리함
  • 로컬 환경뿐만 아니라 클러스터 환경에서 대규모 데이터 처리를 효율적으로 분산 실행할 수 있음
  •  docs : https://docs.ray.io/en/latest/ray-core/walkthrough.html?_gl=1*1e3lt4l*_ga*ODU4NzkwMDUxLjE3MjY3OTQxMjE.*_up*MQ..
# Define the square task.
@ray.remote
def square(x):
    return x * x

# Launch four parallel square tasks.
futures = [square.remote(i) for i in range(4)]

# Retrieve results.
print(ray.get(futures))
# -> [0, 1, 4, 9]

[3] Dask

  • 대규모 데이터 분석과 병렬 처리를 위한 동적 작업 스케줄러를 제공하는 라이브러리
  • Pandas, NumPy와 호환이 되므로 PandasNumPy 작업을 확장할 필요가 있을 때 매우 유용함
  • docs : https://docs.dask.org/en/stable/
import dask.array as da

x = da.random.random((10000, 10000), chunks=(1000, 1000))
result = x.mean().compute()  # Dask로 병렬 처리
print(result)

[4] ProcessPoolExecutor

  • Python의 concurrent.futures 모듈에 있는 고급 병렬 처리 라이브러리로, 다중 프로세스를 쉽게 관리할 수 있는 기능을 제공
  • multiprocessing과 유사하지만 더 높은 수준의 API를 제공해 프로세스를 관리하고 병렬 처리를 쉽게 수행
  • docs : https://docs.python.org/3/library/concurrent.futures.html
from concurrent.futures import ProcessPoolExecutor

def square(x):
    return x * x

with ProcessPoolExecutor() as executor:
    futures = [executor.submit(square, i) for i in range(5)]
    results = [f.result() for f in futures]
    print(results)  # [0, 1, 4, 9, 16]

처리 결과 비교

처리 결과, 제게 주어진 task에서 가장 좋은 성능을 보인 라이브러리는 Ray였습니다.

기존 작업 (병렬 처리 X) 약 26분
Ray 약 6분 (성능 약 77% 향상)
MultiProcessing 약 10분 (성능 약 61.54% 향상)
Dask 기존 시간(26분)을 초과하여 중단
ProcessPoolExecutor 기존 시간(26분)을 초과하여 중단

위와 같은 실험 결과를 토대로 저는 주어진 과제에 레이를 활용하기로 결정했습니다. 실제로 해당 결과를 공유한 이후MultiProcessing보다 레이가 더 빠르다니, 흥미로운 실험 결과라는 코멘트를 받기도 하였습니다 :)

물론 모든 작업에서 반드시 위와 같은 결과를 보이는 것이 절대 아니기 때문에, 과제에 따라 실험을 통해 적합한 라이브러리를 잘 취사 선택하는 것이 중요하겠습니다.


자원 확인

nproc
#128

리눅스 명령어로 확인한 결과 제 서버가 가지고 있는 CPU 코어는 총 128개로 확인이 되었습니다.

ray.init(include_dashboard=True)

 

ray의 init함수를 쓸 때 대시보드 설정을 True로 할당하면, 브라우저에서 localhost:8265에 접속해 대시보드를 볼 수 있습니다. (저는 회사 서버 + 도커 컨테이너를 쓰고 있어서 이 부분은 복잡해서 pass)

import ray

ray.init()

# 현재 사용 가능한 리소스 보기
available_resources = ray.available_resources()
print(available_resources)

위와 같이 available_resources 함수를 실행하면, 사용 중인 CPU와 GPU 자원에 대한 정보가 출력됩니다.

(예) {'accelerator_type:G': 1.0, 'node:__internal_head__': 1.0, 'CPU': 128.0, 'memory': 1058863122432.0, 'object_store_memory': 10000000000.0, 'GPU': 6.0, 'node:172.17.0.8': 1.0}

  • 'CPU': 128.0 -> 이 시스템에 128개의 CPU 코어가 사용 가능하다는 것을 나타냅니다. nproc 명령어에서 확인한 128개의 코어 수와 일치하네요. Ray는 이 128개의 코어를 병렬 처리에 사용합니다.
  • 'memory': 1058863122432.0 -> 이 값은 총 사용 가능한 메모리를 바이트 단위로 나타내고, 제게 주어진 시스템에는 약 1TB의 메모리가 사용 가능하네요.
  • 'object_store_memory': 10000000000.0 -> 이 메모리는 Ray에서 객체를 저장하는 데 사용됩니다.  10GB가 설정되어 있네요.

이상으로 CPU의 병렬 처리에 대한 간략한 포스팅을 마치도록 하겠습니다. 주어진 리소스를 잘 파악하고 100% 활용할 수 있도록 늘 고민하는 엔지니어가 될 수 있도록 오늘도 내일도 열심히 공부하고 노력하겠습니다 :)

감사합니다!

+ Recent posts