[모던 CMake] CUDA 프로젝트 구성과 빌드

이번 글에서는 CMake를 사용하여 CUDA 기반의 GPU 가속 응용 프로그램을 빌드하고 설정하는 방법을 알아보겠습니다. GPU 프로그래밍은 고성능 계산, 머신 러닝, 그래픽스 등 다양한 분야에서 중요한 역할을 합니다. CMake를 활용하여 CUDA 코드를 효율적으로 관리하고 빌드 시스템에 통합하는 방법을 살펴보겠습니다.

CUDA와 CMake의 통합

CUDA는 NVIDIA에서 개발한 병렬 컴퓨팅 플랫폼으로, GPU를 활용하여 계산을 가속화할 수 있습니다. CMake는 CUDA를 지원하기 위한 다양한 기능을 제공하며, 이를 통해 CUDA 코드와 C++ 코드를 함께 빌드할 수 있습니다.

CMake에서 CUDA 활성화

CMake에서 CUDA를 사용하기 위해서는 프로젝트 설정에서 LANGUAGES에 CUDA를 추가합니다.

cmake_minimum_required(VERSION 3.18)
project(MyCUDAProject LANGUAGES CXX CUDA)
  • CMake 3.8 이상부터 CUDA를 언어로서 직접 지원합니다.
  • CMake 3.17 이전 버전에서는 find_package(CUDA REQUIRED)를 사용해야 했습니다.

간단한 CUDA 프로젝트 구성

디렉토리 구조

my_cuda_project/
├── CMakeLists.txt
├── src/
│   ├── CMakeLists.txt
│   ├── main.cpp
│   └── kernel.cu
└── include/
    └── kernel.h

최상위 CMakeLists.txt

cmake_minimum_required(VERSION 3.18)
project(MyCUDAProject LANGUAGES CXX CUDA)

add_subdirectory(src)

src/CMakeLists.txt

add_executable(MyCUDAApp main.cpp kernel.cu)

# CUDA 아키텍처 설정 (필요에 따라 수정)
set_target_properties(MyCUDAApp PROPERTIES
    CUDA_ARCHITECTURES 60 70 75 80)

# CUDA 표준 설정 (CUDA 11부터 지원)
set_target_properties(MyCUDAApp PROPERTIES
    CUDA_STANDARD 14
    CUDA_STANDARD_REQUIRED ON)
  • CUDA_ARCHITECTURES: 타겟 GPU의 아키텍처를 지정합니다. 예를 들어, 60은 Pascal(6.0), 70은 Volta(7.0)를 의미합니다.
  • CUDA_STANDARD: CUDA 코드에서 사용할 C++ 표준을 지정합니다.

kernel.cu 예제

#include "kernel.h"
#include <cuda_runtime.h>

__global__ void addKernel(int *c, const int *a, const int *b) {
    int i = threadIdx.x;
    c[i] = a[i] + b[i];
}

void addWithCUDA(int *c, const int *a, const int *b, size_t size) {
    int *dev_a = nullptr;
    int *dev_b = nullptr;
    int *dev_c = nullptr;

    cudaMalloc((void**)&dev_a, size * sizeof(int));
    cudaMalloc((void**)&dev_b, size * sizeof(int));
    cudaMalloc((void**)&dev_c, size * sizeof(int));

    cudaMemcpy(dev_a, a, size * sizeof(int), cudaMemcpyHostToDevice);
    cudaMemcpy(dev_b, b, size * sizeof(int), cudaMemcpyHostToDevice);

    addKernel<<<1, size>>>(dev_c, dev_a, dev_b);

    cudaMemcpy(c, dev_c, size * sizeof(int), cudaMemcpyDeviceToHost);

    cudaFree(dev_a);
    cudaFree(dev_b);
    cudaFree(dev_c);
}

kernel.h 예제

#pragma once

void addWithCUDA(int *c, const int *a, const int *b, size_t size);

main.cpp 예제

#include <iostream>
#include "kernel.h"

int main() {
    const int arraySize = 5;
    const int a[arraySize] = {1, 2, 3, 4, 5};
    const int b[arraySize] = {10, 20, 30, 40, 50};
    int c[arraySize] = {0};

    addWithCUDA(c, a, b, arraySize);

    std::cout << "Result: ";
    for (int i = 0; i < arraySize; ++i) {
        std::cout << c[i] << " ";
    }
    std::cout << std::endl;

    return 0;
}

빌드 및 실행

mkdir build
cd build
cmake ..
make
./MyCUDAApp
  • GPU 드라이버와 CUDA Toolkit이 설치되어 있어야 합니다.
  • 실행 결과는 배열의 요소별 합계를 출력합니다.

CUDA 설정 옵션

CUDA 아키텍처 설정

CUDA_ARCHITECTURES를 설정하여 타겟 GPU 아키텍처를 지정합니다.

set_target_properties(MyCUDAApp PROPERTIES
    CUDA_ARCHITECTURES 75)
  • 컴파일 시간을 줄이기 위해 실제 사용하는 아키텍처만 지정하는 것이 좋습니다.
  • 여러 아키텍처를 지원하려면 리스트로 지정합니다.

CUDA 컴파일 옵션

추가적인 컴파일 옵션을 설정할 수 있습니다.

target_compile_options(MyCUDAApp PRIVATE $<$<COMPILE_LANGUAGE:CUDA>:
    --use_fast_math
    --expt-relaxed-constexpr
>)
  • $<$<COMPILE_LANGUAGE:CUDA>:...>는 CUDA 소스 파일에만 옵션을 적용합니다.
  • --use_fast_math: 빠른 수학 함수 사용
  • --expt-relaxed-constexpr: CUDA에서 constexpr 지원 활성화

CUDA 런타임 라이브러리 설정

CUDA 런타임 라이브러리를 정적으로 또는 동적으로 링크할 수 있습니다.

set_target_properties(MyCUDAApp PROPERTIES
    CUDA_RUNTIME_LIBRARY Static)
  • Static: 정적 링크
  • Shared: 동적 링크

CUDA와 C++ 혼합 프로젝트

CUDA 코드와 C++ 코드를 함께 사용하는 경우, 소스 파일의 컴파일 언어를 명시적으로 지정해야 할 수 있습니다.

set_source_files_properties(
    kernel.cu
    PROPERTIES
    LANGUAGE CUDA
)
  • CMake는 .cu 확장자를 자동으로 CUDA로 인식하지만, 필요 시 명시적으로 설정합니다.

CUDA 라이브러리 사용

cuBLAS, cuFFT 등 NVIDIA CUDA 라이브러리 사용

CUDA Toolkit에서 제공하는 라이브러리를 사용할 수 있습니다.

find_package(CUDAToolkit REQUIRED)

target_link_libraries(MyCUDAApp PRIVATE CUDA::cublas CUDA::cufft)
  • find_package(CUDAToolkit REQUIRED): CUDA Toolkit을 찾습니다.
  • CUDA::cublas, CUDA::cufft: cuBLAS와 cuFFT 라이브러리를 링크합니다.

CUDA와 모던 CMake 기능 결합

target_compile_features() 사용

CUDA 코드에서도 C++ 표준 기능을 사용할 수 있습니다.

target_compile_features(MyCUDAApp PRIVATE cxx_std_14)
  • CUDA 11부터는 C++14 표준을 지원합니다.

통합 빌드 시스템 구축

CUDA 코드와 C++ 코드를 통합하여 하나의 빌드 시스템에서 관리할 수 있습니다.

add_library(MyLib STATIC mylib.cpp kernel.cu)

set_target_properties(MyLib PROPERTIES
    CUDA_ARCHITECTURES 75
    CUDA_SEPARABLE_COMPILATION ON
)

target_link_libraries(MyLib PUBLIC CUDA::cudart)
  • CUDA_SEPARABLE_COMPILATION ON: CUDA의 분리 컴파일 기능 활성화
  • 라이브러리를 다른 타겟에서 재사용할 수 있습니다.

주의사항

  • CUDA 버전 호환성: 사용하는 CUDA Toolkit 버전과 GPU 드라이버의 호환성을 확인해야 합니다.
  • 컴파일러 지원: CUDA는 특정 컴파일러 버전을 지원하므로, 컴파일러와의 호환성을 검토해야 합니다.
  • 플랫폼 지원: CUDA는 NVIDIA GPU가 설치된 시스템에서만 사용할 수 있습니다.

참고 자료

반응형