SYCL 입문 시리즈의 세 번째 글입니다. 이전 글(#2)에서 NVIDIA GPU나 Qualcomm GPU를 대상으로 SYCL 코드를 실행하기 위한 백엔드 선택, 디바이스 셀렉터 사용 방법을 살펴봤습니다. 이제 한 단계 더 나아가, SYCL의 메모리 모델과 커널 작성 패턴을 자세히 다루겠습니다. 이 글은 여전히 입문자를 대상으로 하며, 가능한 한 모든 단계를 구체적으로 설명하여, 독자가 직접 따라 할 수 있도록 합니다.
SYCL에서 메모리와 커널(디바이스 코드) 작성은 SYCL이 제공하는 추상화 덕분에 C++ 람다 표기법을 활용하고, 버퍼(buffer)와 액세서(accessor)를 통해 호스트-디바이스 데이터를 쉽게 관리할 수 있습니다. 또한 워크아이템(work-item), 워크그룹(work-group), ND-Range(nd_range) 개념을 간단한 예제를 통해 이해해보겠습니다.
목표
- SYCL 메모리 모델: 버퍼(buffer), 액세서(accessor), 호스트-디바이스 메모리 전송 자동 관리 이해
- 람다 함수를 이용한 SYCL 커널 작성: C++ 스타일로 GPU 커널 구현
- 워크아이템(Work-item), 워크그룹(Work-group), ND-Range 개념 소개
- 간단한 2D 데이터 처리 예제를 통해 SYCL 커널 패턴 실습
- CMake 빌드 & 이전 글에서 사용한 디바이스 선택/백엔드 설정 응용
SYCL 메모리 모델 개념
SYCL은 프로그래머가 명시적으로 memcpy
같은 것을 호출하지 않아도, 버퍼(buffer) 객체를 통해 호스트 메모리를 디바이스 메모리로 자동 전송하고, 커널 실행 후 결과를 다시 호스트 메모리에 반영합니다.
핵심 개념:
- 버퍼(buffer): 호스트 메모리에 있는 STL 컨테이너(예: std::vector)를 SYCL 범위(range)와 연계해 디바이스 메모리와 동기화하는 추상화 객체.
- 액세서(accessor): 커널 내부에서 디바이스 메모리에 접근하기 위한 핸들.
get_access<mode>(cgh)
를 통해 읽기, 쓰기 모드를 지정하면 SYCL 런타임이 메모리 전송을 관리. - 스코프(scope)를 벗어날 때 버퍼의 데이터가 자동으로 호스트로 반영.
이로써 개발자는 데이터 전송 로직을 대부분 신경 쓰지 않고, 단지 버퍼와 액세서를 선언해주면 됩니다.
커널 작성: C++ 람다 이용
SYCL에서 커널은 C++ 람다 표현식으로 작성할 수 있습니다.
예:
cgh.parallel_for<class simple_kernel>(sycl::range<1>(N), [=](sycl::id<1> i) {
acc[i] = acc[i] * 2.0f;
});
parallel_for
에 람다를 전달하면 SYCL 컴파일러가 이를 디바이스 코드로 컴파일class simple_kernel
은 커널 식별자 역할(고유 클래스명 필요)range<1>(N)
은 1차원 범위,i
는 인덱스- 2D나 3D 데이터처리를 위해
range<2>
나range<3>
사용 가능
워크아이템 & 워크그룹
- 워크아이템(work-item): 병렬로 실행되는 단위(예: i 인덱스)
- 워크그룹(work-group): 워크아이템들을 그룹으로 묶은 단위
parallel_for
는 ND-Range(nd_range) 개념을 통해 워크그룹 크기와 전체 범위를 지정할 수 있음.
예:
cgh.parallel_for<class kernel2d>(
sycl::nd_range<2>(sycl::range<2>(1024, 1024), sycl::range<2>(16,16)),
[=](sycl::nd_item<2> item) {
auto i = item.get_global_id(0);
auto j = item.get_global_id(1);
// 2D 인덱스 (i,j)에 대한 연산
}
);
이 예제에서는 1024x1024 워크아이템을 16x16 워크그룹으로 나누어 실행.
예제 코드 (2D 데이터 처리)
아래는 2D 배열(1024x1024)을 SYCL 커널로 처리하는 간단한 예제입니다. 모든 요소를 3배로 만들고, 결과를 호스트에 반영한 뒤 출력합니다.
디렉토리 구조
my_sycl_kernel/
├─ CMakeLists.txt
└─ src/
└─ main.cpp
main.cpp 코드
#include <CL/sycl.hpp>
#include <iostream>
#include <vector>
int main() {
try {
// 이전 글에서 설명한 device_selector나 백엔드 설정을 여기서 응용 가능
// 일단 기본 device_selector 사용
sycl::queue q;
std::cout << "Running on: " << q.get_device().get_info<sycl::info::device::name>() << "\n";
const size_t N = 1024;
std::vector<float> data(N*N, 1.0f); // 1024x1024 요소, 모두 1.0
{
sycl::buffer<float,2> buf(data.data(), sycl::range<2>(N, N));
q.submit([&](sycl::handler& cgh) {
auto acc = buf.get_access<sycl::access::mode::read_write>(cgh);
cgh.parallel_for<class multiply_by_three>(
sycl::range<2>(N, N),
[=](sycl::id<2> idx) {
acc[idx] *= 3.0f;
}
);
});
// 커맨드 그룹 범위 끝나면 데이터가 호스트로 반영
}
// 결과 확인
std::cout << "data[0,0] = " << data[0] << ", data[100,100] = " << data[100*N+100] << "\n";
std::cout << "All elements should now be 3.0\n";
} catch(sycl::exception const &e) {
std::cerr << "SYCL Exception: " << e.what() << "\n";
return 1;
}
return 0;
}
CMakeLists.txt 예제
cmake_minimum_required(VERSION 3.10)
project(kernel_example CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(NOT DEFINED SYCL_COMPILER)
set(SYCL_COMPILER "dpcpp") # 예: oneAPI DPC++
endif()
set(CMAKE_CXX_COMPILER ${SYCL_COMPILER})
add_executable(kernel_example src/main.cpp)
빌드 & 실행:
mkdir build
cd build
cmake -D SYCL_COMPILER=dpcpp ..
make
./kernel_example
출력 예시:
Running on: Intel(R) UHD Graphics [0x3ea0]
data[0,0] = 3, data[100,100] = 3
All elements should now be 3.0
NVIDIA나 Qualcomm GPU를 활용하려면 이전 글(#2)에서 설명한 백엔드 옵션, device_selector를 변경한 뒤 빌드하면 동일 코드가 해당 GPU에서 실행될 수 있습니다.
정리 및 다음 글 예고
이번 글에서는 SYCL 메모리 모델(버퍼, 액세서)과 커널 작성 패턴(C++ 람다 이용), 워크아이템/워크그룹 개념 등을 다루어, SYCL 코드가 단순한 1D 연산을 넘어 2D 데이터 처리까지 확장 가능함을 보았습니다. 이제 다양한 형태의 데이터를 SYCL을 통해 간단하게 처리할 수 있음을 이해했으리라 생각합니다.
다음 글(#4)에서는 SYCL에서 더 복잡한 메모리 관리 기법이나 ND-Range 구성을 다루며, 성능 최적화와 백엔드별 최적화 전략을 살짝 엿보는 과정을 진행할 예정입니다. 또한 호스트 코드에서 디바이스 코드로 데이터를 넘길 때 고려해야 할 점들을 좀 더 깊게 파고들 예정입니다.
'개발 이야기 > SYCL (시클)' 카테고리의 다른 글
[SYCL 입문 #4] ND-Range 활용과 메모리 최적화 기법 소개 (0) | 2024.12.19 |
---|---|
[SYCL 입문 #2] NVIDIA와 Qualcomm GPU에서 SYCL 코드 실행하기 (1) | 2024.12.19 |
[SYCL 입문 #1] SYCL 소개 및 개발 환경 준비, Hello SYCL 예제 (0) | 2024.12.19 |