CPU와 GPU 성능 최적화 소개
고성능 컴퓨팅 및 대규모 병렬 작업 처리에서 GPU는 필수적인 가속기가 되었습니다. GPU 컴퓨팅 성능을 최대한 활용하려면 CPU 코어를 GPU에 합리적으로 할당하고 바인딩하여 CPU와 GPU 관계를 최적화하는 것이 중요합니다. 이 글에서는 소켓과 NUMA(Non-Uniform Memory Access) 개념을 자세히 살펴보고, 이러한 하드웨어 아키텍처를 기반으로 CPU 및 GPU 코어 바인딩을 구현하여 최적의 시스템 성능을 보장하는 방법에 대해 논의합니다.
소켓 개념
소켓이란?
소켓은 일반적으로 마더보드의 물리적 CPU 설치 슬롯을 의미합니다. 각 소켓은 하나의 물리적 CPU에 해당하며, 일반적으로 여러 코어와 하나 이상의 캐시 레벨(예: L1, L2, L3 캐시)을 포함합니다. 다중 소켓 시스템(예: 듀얼 또는 쿼드 서버)에서 각 소켓은 물리적 CPU를 가지며, 고속 상호 연결(예: Intel의 QPI 또는 AMD의 Infinity Fabric)을 통해 연결됩니다.
다중 소켓 시스템의 특성
다중 소켓 시스템에서 각 소켓의 CPU는 자체 로컬 메모리에 액세스할 수 있을 뿐만 아니라 다른 소켓의 메모리에도 액세스할 수 있습니다. 이러한 메모리 액세스 패턴은 메모리 액세스 효율을 최적화하기 위해 NUMA 개념을 도입합니다.
NUMA(Non-Uniform Memory Access) 아키텍처
NUMA란?
NUMA는 Non-Uniform Memory Access의 약자로, 비균일 메모리 액세스를 의미합니다. 기존의 균일 메모리 액세스(UMA)와 달리 NUMA 아키텍처에서는 시스템 메모리가 여러 영역으로 나뉘며, 각 영역은 특정 CPU(소켓)와 연결됩니다. 로컬 메모리(동일 소켓 내)에 액세스하는 것이 원격 메모리(다른 소켓)에 액세스하는 것보다 빠르므로 지연 시간이 더 낮습니다.
NUMA 노드와 메모리 액세스 지연
NUMA 시스템에서 각 소켓과 직접 연결된 메모리는 NUMA 노드를 형성합니다. 동일한 NUMA 노드 내의 메모리 액세스는 더 빠르지만, 노드 간 메모리 액세스는 추가 버스 전송으로 인해 지연 시간이 더 높습니다. 메모리 및 CPU 어피니티를 최적화하고 작업이 동일한 NUMA 노드 내에서 실행되도록 하는 것은 중요한 성능 최적화 단계입니다.
CPU와 GPU의 물리적 관계
GPU 하드웨어 아키텍처
GPU는 일반적으로 PCIe(Peripheral Component Interconnect Express) 버스를 통해 CPU와 통신합니다. 다중 소켓 시스템에서 GPU는 일반적으로 하나의 소켓(및 해당 NUMA 노드)에만 연결되며 소켓 간에 연결되지 않습니다. 즉, 실제 작동에서 GPU는 연결된 소켓의 CPU 코어 및 메모리와 더 높은 대역폭과 낮은 지연 시간을 가집니다.
CPU와 GPU 어피니티
CPU와 GPU 통신은 주로 데이터 전송에 의존합니다. 데이터는 CPU에서 GPU로, 그리고 다시 CPU로 전송되며, 이는 성능에 큰 영향을 미치는 메모리 액세스 작업을 수반합니다. GPU에 바인딩된 CPU 코어가 GPU와 동일한 NUMA 노드 내에 있으면 데이터 전송 지연 시간이 크게 줄어듭니다. 따라서 CPU 코어를 GPU에 바인딩하는 것은 핵심적인 성능 최적화 단계입니다.
어피니티 기반 GPU 및 CPU 코어 바인딩 전략
Docker 컨테이너화된 배포에서 GPU 및 CPU 어피니티 바인딩을 구현하는 것은 컨테이너화된 작업 성능을 향상시키는 데 중요합니다. Docker의 CPU 및 GPU 리소스 제어 기능을 사용하면 컨테이너에서 사용하는 CPU 코어와 GPU를 정밀하게 제어할 수 있습니다.
컨테이너 CPU 및 GPU 리소스 할당
Docker 컨테이너는 할당된 CPU 및 GPU 리소스에 대한 정밀한 제어를 허용합니다. 컨테이너에서 사용하는 CPU 코어와 GPU 장치를 지정하면 어피니티 바인딩을 구현하여 성능을 최적화할 수 있습니다.
Docker CPU 설정
Docker에서 CPU 리소스 할당은 다음 매개변수를 사용하여 제어할 수 있습니다.
--cpuset-cpus: 컨테이너가 사용할 수 있는 물리적 CPU 코어를 지정합니다. 예를 들어--cpuset-cpus="0-3"은 컨테이너를 CPU 코어 0~3으로 제한합니다.--cpu-shares: 컨테이너의 CPU 사용 가중치를 제어하지만 특정 코어 사용을 제한하지는 않습니다.--cpus: 컨테이너가 사용할 수 있는 총 CPU 코어 수(가상 코어)를 제한합니다.
Docker GPU 설정
GPU 장치 바인딩은 다음 Docker 매개변수를 사용하여 달성할 수 있습니다.
--gpus: 컨테이너가 액세스할 수 있는 GPU 장치를 지정합니다. 예를 들어--gpus '"device=0"'는 GPU 0을 컨테이너에 할당합니다.
Docker에서 GPU 및 CPU 코어 바인딩 구현
Docker 컨테이너에서 GPU 및 CPU 코어 바인딩을 구현하려면 CPU 및 GPU 설정을 결합합니다. 다음은 Docker 컨테이너를 시작할 때 특정 CPU 코어와 GPU를 바인딩하는 방법의 예입니다.
docker run --cpuset-cpus="0-3" --gpus '"device=0"' --memory="8g" my_gpu_container
이 명령에서:
--cpuset-cpus="0-3"은 컨테이너를 CPU 코어 0~3에 바인딩하며, 이는 GPU 0과 동일한 NUMA 노드 내에 있어야 합니다.--gpus '"device=0"'는 GPU 0을 컨테이너에 할당합니다.--memory="8g"는 컨테이너의 메모리 사용량을 8GB로 제한하여 메모리 할당이 CPU/GPU 어피니티와 일치하도록 합니다.
최적 바인딩 달성
컨테이너에서 최적 바인딩을 보장하려면 먼저 머신의 물리적 토폴로지를 확인합니다. nvidia-smi topo -m을 사용하여 머신의 토폴로지를 확인할 수 있습니다.
nvidia-smi topo -m
출력에서 머신의 NUMA 구성과 GPU 할당을 확인할 수 있습니다. 예를 들어, 머신에 두 개의 NUMA 노드가 있고, NUMA 0에 4개의 GPU, NUMA 1에 4개의 GPU가 있는 경우 각 NUMA 노드에 대한 GPU 및 CPU 어피니티를 식별할 수 있습니다.
어피니티 계산을 위한 의사 코드
다음은 어피니티를 계산하고 GPU 및 CPU ID를 결정하기 위한 간소화된 의사 코드입니다.
type Affinity struct {
}
// Calculate whether the affinity is satisfied and return the corresponding GPU and CPU IDs
func calAffinity(affinity *Affinity, cpuUse []int, gpuUse []int, gpuReq int, cpuReq int) (bool, []int, []int) {
return true, []int{}, []int{}
}
이 방법은 노드가 최적 바인딩 요구 사항을 충족하는지 확인하는 데 사용할 수 있습니다. 여러 노드가 존재하는 경우 이 방법을 사용하여 가장 적합한 노드를 평가하고 선택할 수 있습니다.
Novita AI는 차세대 AI 컴퓨팅에 맞춤화된 컨테이너 엔진을 개발하여 알고리즘을 동적으로 조정하고 하드웨어 사용량을 실시간으로 모니터링하며 엔드투엔드 최적화를 수행합니다. 사용자는 NUMA 기술 세부 사항에 대해 걱정하지 않고 최고의 컴퓨팅 성능을 누릴 수 있습니다.
사용자는 제어판에서 더 고급 NUMA 설정을 수행할 수도 있습니다. 더 많은 관련 요구 사항이 있으면 Discord에서 언제든지 문의해 주세요!
자세한 내용은 Novita AI를 방문하세요!

