SYCL 입문 시리즈의 두 번째 글입니다. 지난 글(#1)에서는 SYCL 개념 소개, Conda 및 Windows 환경 설정, CMake 빌드, 그리고 “Hello SYCL!” 예제를 CPU나 가능한 디바이스에서 실행하는 방법을 살펴봤습니다. 이제 한 단계 더 나아가, NVIDIA GPU나 Qualcomm GPU에서 SYCL 코드를 실행하는 전략을 다루겠습니다. 이는 SYCL의 큰 장점 중 하나인 이식성을 체험하기 위한 핵심입니다.
이번 글에서는 초보자도 따라 할 수 있도록 구현체(백엔드) 선택, 디바이스 선택 로직, CMake 빌드 옵션 등을 구체적으로 제시하겠습니다. 앞서 구축한 Conda 환경이나 Windows 환경에서도 적용 가능한 형태로 설명하니, OS나 개발자 환경에 맞게 응용해보시기 바랍니다.
목표
- NVIDIA GPU 지원: CUDA 백엔드를 통한 SYCL 코드 실행 전략
- Qualcomm GPU 지원: OpenCL 백엔드(또는 Level Zero 백엔드) 활용 가능 여부 확인
device_selector
를 활용해 특정 벤더 디바이스를 우선 선택하는 방법- CMake 빌드 시 백엔드를 전환하는 옵션 예제
- 이전 글의 “Hello SYCL!” 예제를 백엔드만 바꿔서 NVIDIA나 Qualcomm GPU에서 실행하는 방법
사전 준비 및 구현체 선택
앞서 설치했던 SYCL 구현체 중 하나를 사용합니다.
- oneAPI DPC++: Intel에서 만든 SYCL 구현체. Intel GPU나 CPU 타겟은 잘 지원하나, NVIDIA GPU 지원은 Experimental일 수 있습니다. Qualcomm GPU 지원은 OpenCL 백엔드가 제공된다면 가능할 수도 있습니다.
- hipSYCL: LLVM/Clang 기반 SYCL 구현으로, CUDA 백엔드를 잘 지원합니다(즉, NVIDIA GPU에 적합). OpenCL 백엔드도 지원하므로, Qualcomm GPU가 OpenCL 플랫폼으로 노출된다면 접근할 수 있습니다.
초보자는 일단 hipSYCL를 통한 CUDA 백엔드로 NVIDIA GPU 실행부터 시도하는 것이 비교적 쉬울 수 있습니다. Qualcomm GPU는 좀 더 특수한 환경일 수 있으므로 OpenCL 런타임 및 드라이버가 필요합니다.
NVIDIA GPU 지원 (hipSYCL 예시)
hipSYCL 사용 시 CUDA 백엔드를 활성화하려면 컴파일 시 다음과 같은 옵션을 줄 수 있습니다.
syclcc -O2 -fsycl-targets=nvptx64-nvidia-cuda -o hello_nvidia hello_sycl.cpp
CMake를 사용할 경우 CMakeLists.txt에 다음과 같이 백엔드를 지정하는 변수를 둘 수 있습니다.
if(BACKEND STREQUAL "cuda")
set(BACKEND_FLAGS "-fsycl-targets=nvptx64-nvidia-cuda")
elseif(BACKEND STREQUAL "opencl")
set(BACKEND_FLAGS "-fsycl-targets=spir64")
endif()
이렇게 하면 cmake -D BACKEND=cuda ..
명령을 통해 NVIDIA GPU용 바이너리를 빌드할 수 있습니다.
Qualcomm GPU 지원 (OpenCL 백엔드)
Qualcomm GPU를 SYCL로 활용하려면 OpenCL 런타임 및 드라이버가 설치되어야 합니다. Qualcomm 장치(예: 임베디드 디바이스나 특정 개발 보드)에서 OpenCL이 동작한다면, hipSYCL나 DPC++의 OpenCL 백엔드를 통해 접근 가능합니다.
cmake -D BACKEND=opencl ..
make
./hello_sycl
이렇게 하면 OpenCL 백엔드로 빌드한 SYCL 코드가 OpenCL 디바이스를 탐색하고, 그 중 Qualcomm GPU 디바이스를 찾을 수 있다면 선택해서 실행할 수 있습니다.
device_selector 커스터마이징
SYCL에서 특정 벤더 디바이스를 우선 선택하려면 device_selector
를 오버라이드할 수 있습니다.
struct nvidia_device_selector : public sycl::device_selector {
int operator()(const sycl::device &dev) const override {
auto vendor = dev.get_info<sycl::info::device::vendor>();
// NVIDIA GPU는 보통 "NVIDIA" 문자열 포함
if (vendor.find("NVIDIA") != std::string::npos) {
return 1;
}
return -1;
}
};
struct qualcomm_device_selector : public sycl::device_selector {
int operator()(const sycl::device &dev) const override {
auto vendor = dev.get_info<sycl::info::device::vendor>();
// Qualcomm GPU는 "Qualcomm" 또는 "Adreno"등의 문자열일 수 있음
// 실제 vendor 문자열을 확인한 뒤 수정 필요
if (vendor.find("Qualcomm") != std::string::npos || vendor.find("Adreno") != std::string::npos) {
return 1;
}
return -1;
}
};
이렇게 만든 셀렉터를 사용하면:
// NVIDIA GPU용
sycl::queue q(nvidia_device_selector());
// Qualcomm GPU용
sycl::queue q(qualcomm_device_selector());
해당 벤더 디바이스를 우선적으로 선택하려고 시도합니다(없다면 다른 디바이스로 fallback할 수 있음).
예제 코드 재활용
이전 글에서 사용한 “Hello SYCL!” 예제를 약간 수정해서 NVIDIA GPU나 Qualcomm GPU를 노리도록 합니다. main.cpp
를 다음과 같이 바꿔봅시다.
#include <CL/sycl.hpp>
#include <iostream>
#include <vector>
#include <string>
// 간단히 NVIDIA 또는 Qualcomm 셀렉터 중 하나를 사용
struct nvidia_device_selector : public sycl::device_selector {
int operator()(const sycl::device &dev) const override {
auto vendor = dev.get_info<sycl::info::device::vendor>();
if (vendor.find("NVIDIA") != std::string::npos) return 1;
return -1;
}
};
struct qualcomm_device_selector : public sycl::device_selector {
int operator()(const sycl::device &dev) const override {
auto vendor = dev.get_info<sycl::info::device::vendor>();
// 실제 벤더명 확인 필요
if (vendor.find("Qualcomm") != std::string::npos || vendor.find("Adreno") != std::string::npos)
return 1;
return -1;
}
};
int main() {
try {
// 여기서 원하는 셀렉터 선택: 예) NVIDIA GPU
sycl::queue q(nvidia_device_selector());
std::cout << "Running on: " << q.get_device().get_info<sycl::info::device::name>() << "\n";
size_t N = 1024;
std::vector<float> data(N, 1.0f);
{
sycl::buffer<float,1> buf(data.data(), sycl::range<1>(N));
q.submit([&](sycl::handler& cgh) {
auto acc = buf.get_access<sycl::access::mode::read_write>(cgh);
cgh.parallel_for<class simple_kernel>(sycl::range<1>(N), [=](sycl::id<1> i) {
acc[i] *= 2.0f;
});
});
}
std::cout << "data[0] = " << data[0] << ", data[100] = " << data[100] << "\n";
std::cout << "All elements should now be 2.0\n";
} catch (sycl::exception const &e) {
std::cerr << "SYCL Exception: " << e.what() << "\n";
return 1;
}
return 0;
}
CMake 빌드 시:
mkdir build
cd build
cmake -D SYCL_COMPILER=syclcc -D BACKEND=cuda ..
make
./hello_sycl
위와 같이 하면 CUDA 백엔드를 사용하므로 NVIDIA GPU가 인식되어 있다면 해당 디바이스에서 코드가 실행될 수 있습니다. Qualcomm GPU에서는 OpenCL 백엔드로 빌드한 뒤 qualcomm_device_selector
를 사용하면 됩니다.
주의 사항
- Qualcomm GPU 지원은 시나리오에 따라 차이가 큽니다. OpenCL 기반 런타임이 제대로 설치되어 있어야 하고, SYCL 구현체가 해당 OpenCL 디바이스를 인식해야 합니다. 임베디드 환경이나 특정 개발 보드에서는 별도의 드라이버 설정이 필요할 수 있습니다.
- NVIDIA GPU에서 잘 동작하는 코드를 Qualcomm GPU에서도 동일하게 돌리려면, 실제로 해당 디바이스에서 OpenCL 디바이스 벤더 확인, 커널 실행 등의 테스트를 해야 합니다.
- 디바이스 셀렉터는 단지 우선순위를 부여할 뿐, 해당 벤더 디바이스가 없으면 CPU나 다른 디바이스로 fallback할 수 있습니다. 출력 메시지를 통해 어떤 디바이스가 선택됐는지 확인하세요.
정리 및 다음 글 예고
이번 글에서는 SYCL 코드가 단지 CPU나 Intel GPU뿐 아니라, NVIDIA 및 Qualcomm GPU에서도 실행 가능하다는 점을 확인했습니다. CUDA 백엔드를 이용한 NVIDIA GPU 지원, OpenCL 백엔드를 통한 Qualcomm GPU 접근 전략, 그리고 device_selector 커스터마이징으로 특정 벤더 디바이스를 우선 선택하는 방법을 소개했습니다.
다음 글(#3)에서는 SYCL 메모리 모델과 커널 작성 패턴을 좀 더 자세히 살펴볼 예정입니다. 메모리 관리(버퍼, 액세서), C++ 람다를 이용한 커널 작성법, 워크아이템/워크그룹 개념 등을 익혀, 더 복잡한 연산을 구현하는 기반을 마련해보겠습니다.
'개발 이야기 > SYCL (시클)' 카테고리의 다른 글
[SYCL 입문 #4] ND-Range 활용과 메모리 최적화 기법 소개 (0) | 2024.12.19 |
---|---|
[SYCL 입문 #3] 메모리 모델 & 커널 작성 패턴 이해하기 (0) | 2024.12.19 |
[SYCL 입문 #1] SYCL 소개 및 개발 환경 준비, Hello SYCL 예제 (0) | 2024.12.19 |