안녕하세요, 지난 글에서 OpenCL 개발환경을 준비하고 “Hello OpenCL!” 예제를 통해 간단한 프로그램을 실행하는 과정을 살펴봤어요. 이번 글에서는 본격적으로 OpenCL의 큰 그림을 더 명확히 그려볼게요. OpenCL은 플랫폼(Platform)과 디바이스(Device)라는 개념을 통해 다양한 하드웨어 리소스를 관리하고, 이를 프로그래머가 직접 선택하고 제어할 수 있는 구조를 가지고 있습니다.
쉽게 말해, 플랫폼은 특정 벤더나 특정 드라이버 환경을 대표하는 개념이고, 디바이스는 실제 연산을 수행할 수 있는 하드웨어(GPU, CPU, FPGA 등)를 가리켜요. CUDA가 NVIDIA GPU 하나를 전제로 간단한 API로 리소스를 추상화했다면, OpenCL은 다양한 하드웨어를 유연하게 다루기 위해 조금 더 일반화된 구조를 취하고 있다고 생각하면 됩니다.
이번 글에서 다룰 내용은 다음과 같아요.
- OpenCL 플랫폼과 디바이스 개념 정리
- 실제 코드를 통해 플랫폼과 디바이스 정보 얻어오기
- 플랫폼, 디바이스 정보를 바탕으로 어떤 디바이스를 선택할지 결정하는 로직 예제
- CUDA 대비 OpenCL에서의 플랫폼/디바이스 관리 차이점 언급
1. OpenCL 플랫폼(Platform)이란?
OpenCL 플랫폼은 특정 벤더(예: NVIDIA, Intel, AMD)가 제공하는 OpenCL 구현 체계를 대표해요.
한 시스템에 NVIDIA 드라이버가 깔려 있고, 또 Intel GPU나 CPU에 대한 OpenCL 런타임이 있다면, 시스템 내에는 두 개 이상의 플랫폼이 존재할 수 있습니다.
- 예를 들면:
- Platform 0: NVIDIA OpenCL 구현
- Platform 1: Intel OpenCL 구현
각 플랫폼은 자신이 지원하는 디바이스 목록을 가지고 있으며, 디바이스 정보는 플랫폼을 통해 접근합니다.
2. OpenCL 디바이스(Device)란?
디바이스는 실제 계산을 수행할 물리적 또는 논리적 장치입니다. GPU, CPU, Accelerator 등.
OpenCL은 GPU에만 초점을 맞춘 CUDA와 달리 CPU, GPU, DSP, FPGA 등을 디바이스로 간주할 수 있어요. 이렇게 범용적인 하드웨어 지원이 OpenCL의 큰 장점이죠.
플랫폼 당 여러 디바이스가 있을 수 있고, 이를 각각 조회하고, 원하는 디바이스를 골라서 컨텍스트(Context)를 만들고, 명령 큐(Command Queue)를 만들어 커널을 실행하게 됩니다.
3. 플랫폼 & 디바이스 나열하기 (예제)
지난 예제와 마찬가지로 C++20 스타일을 유지하면서, 시스템 내에 어떤 플랫폼과 디바이스들이 있는지 나열해보는 코드를 작성해볼게요. 이 코드를 통해 OpenCL이 얼마나 다양한 디바이스를 접근할 수 있는지 파악할 수 있습니다.
#include <CL/cl.h>
#include <iostream>
#include <vector>
#include <stdexcept>
int main() {
// 플랫폼 개수 확인
cl_uint num_platforms = 0;
clGetPlatformIDs(0, nullptr, &num_platforms);
if (num_platforms == 0) {
std::cerr << "OpenCL 플랫폼을 하나도 찾지 못했어요.\n";
return 1;
}
std::vector<cl_platform_id> platforms(num_platforms);
clGetPlatformIDs(num_platforms, platforms.data(), nullptr);
// 각 플랫폼에 대해 정보 출력
for (cl_uint p = 0; p < num_platforms; ++p) {
char platform_name[128];
clGetPlatformInfo(platforms[p], CL_PLATFORM_NAME, sizeof(platform_name), platform_name, nullptr);
std::cout << "플랫폼 " << p << ": " << platform_name << "\n";
// 해당 플랫폼의 디바이스 나열
cl_uint num_devices = 0;
clGetDeviceIDs(platforms[p], CL_DEVICE_TYPE_ALL, 0, nullptr, &num_devices);
if (num_devices == 0) {
std::cout << " 이 플랫폼엔 디바이스가 없네요.\n";
continue;
}
std::vector<cl_device_id> devices(num_devices);
clGetDeviceIDs(platforms[p], CL_DEVICE_TYPE_ALL, num_devices, devices.data(), nullptr);
for (cl_uint d = 0; d < num_devices; ++d) {
char device_name[128];
clGetDeviceInfo(devices[d], CL_DEVICE_NAME, sizeof(device_name), device_name, nullptr);
std::cout << " 디바이스 " << d << ": " << device_name << "\n";
// 디바이스 타입 확인 (GPU, CPU 등)
cl_device_type device_type;
clGetDeviceInfo(devices[d], CL_DEVICE_TYPE, sizeof(device_type), &device_type, nullptr);
std::cout << " 타입: ";
if (device_type & CL_DEVICE_TYPE_GPU) std::cout << "GPU ";
if (device_type & CL_DEVICE_TYPE_CPU) std::cout << "CPU ";
if (device_type & CL_DEVICE_TYPE_ACCELERATOR) std::cout << "Accelerator ";
if (device_type & CL_DEVICE_TYPE_DEFAULT) std::cout << "Default ";
std::cout << "\n";
}
}
return 0;
}
실제로 빌드하고 실행해보면, 시스템에 설치된 OpenCL 플랫폼 리스트와 각 플랫폼에 속한 디바이스 목록이 쭉 나옵니다. GPU 디바이스가 여러 개이거나, GPU와 CPU 디바이스가 함께 보일 수도 있어요.
이렇게 플랫폼과 디바이스를 직접 나열해보면 “아, 이게 OpenCL이 원하는 방식이구나” 하고 감이 잡히실 거예요. CUDA에 익숙한 분들은 “CUDA는 그냥 cudaGetDeviceCount, cudaGetDeviceProperties 정도면 끝나는데, 여기선 플랫폼도 고르고 디바이스도 골라야 하네?” 라고 생각하실 수 있어요. 맞습니다. 하지만 그만큼 자유도가 높고, 다양한 벤더의 하드웨어를 하나의 코드베이스로 관리할 수 있다는 점이 OpenCL의 강점이에요.
4. 디바이스 선택 로직
현실적인 예로, 우리에게 GPU가 있다면 당연히 GPU를 우선적으로 선택하고 싶겠죠. CPU나 다른 디바이스는 예비로 두거나, 특정한 연산에만 할당하는 식으로 전략을 세울 수 있습니다.
이를 구현하려면 위에서 나열한 디바이스 목록 중 원하는 타입(GPU 우선)을 검색해서 첫 번째 GPU 디바이스를 발견하면 그 디바이스를 선택하는 식으로 하면 돼요. 만약 GPU가 없다면 CPU를 사용하고, 그것도 없다면 에러를 내는 식으로 정책을 세울 수 있습니다.
대략적인 로직은 다음과 같아요.
cl_device_id select_best_device(const std::vector<cl_platform_id>& platforms) {
cl_device_id best_device = nullptr;
for (auto platform : platforms) {
cl_uint num_devices;
clGetDeviceIDs(platform, CL_DEVICE_TYPE_ALL, 0, nullptr, &num_devices);
if (num_devices == 0) continue;
std::vector<cl_device_id> devices(num_devices);
clGetDeviceIDs(platform, CL_DEVICE_TYPE_ALL, num_devices, devices.data(), nullptr);
for (auto dev : devices) {
// GPU를 우선적으로 찾기
cl_device_type device_type;
clGetDeviceInfo(dev, CL_DEVICE_TYPE, sizeof(device_type), &device_type, nullptr);
if (device_type & CL_DEVICE_TYPE_GPU) {
return dev; // 첫 번째 GPU 발견 시 바로 반환
}
// GPU를 못 찾았다면 CPU라도 기억해둘 수 있음
if (!best_device && (device_type & CL_DEVICE_TYPE_CPU)) {
best_device = dev;
}
}
}
return best_device;
}
위 함수는 GPU를 발견하면 즉시 반환하고, GPU가 없으면 CPU라도 기억했다가 마지막에 돌려주는 단순한 로직입니다. 물론 실제 애플리케이션에서는 어떤 벤더의 디바이스를 선호한다던가, 특정 성능 특성을 가진 디바이스를 선택한다거나 하는 더 복잡한 로직을 구현할 수도 있어요.
CUDA는 대부분 NVIDIA GPU 하나만 사용하니 이런 고민이 필요 없었지만, OpenCL에서는 처음에 약간 번거로울 수 있습니다. 그래도 한 번만 잘 짜두면, 어떤 하드웨어에든 코드를 이식할 수 있는 유연함을 얻을 수 있어요.
5. CUDA와 비교하기
- CUDA:
- 목표 하드웨어: NVIDIA GPU
- 초기화: cudaGetDeviceCount, cudaSetDevice 정도로 끝
- 디바이스 선택 로직이 단순 (거의 NVIDIA GPU만 존재하므로)
- OpenCL:
- 목표 하드웨어: 범용(AMD, Intel, NVIDIA, CPU, GPU, FPGA...)
- 초기화: 플랫폼 나열 → 각 플랫폼별 디바이스 나열 → 원하는 디바이스 선택
- 디바이스 선택 로직 직접 구현 필요
CUDA는 특정 하드웨어에 집중하여 개발자가 적은 코드로 바로 GPU 프로그래밍을 시작할 수 있게 해주는 반면, OpenCL은 다양한 환경을 포용하기 위해 더 일반적인 인터페이스를 제공합니다. 결국 무엇을 선택할지는 프로젝트 성격에 따라 다르지만, OpenCL의 접근 방식을 이해해두면 훨씬 넓은 선택지를 가지게 되죠.
6. 마무리
이번 글에서는 OpenCL에서 하드웨어를 관리하는 기반 개념인 플랫폼(Platform)과 디바이스(Device)에 대해 다뤘습니다. 다음 글에서는 이러한 플랫폼과 디바이스를 활용해 컨텍스트(Context), 명령 큐(Command Queue)를 만들고, 실제 연산을 실행하기 위한 준비를 하는 과정을 살펴볼 거예요.
본격적인 커널 작성 전에 왜 이렇게 많은 단계가 필요한지 궁금하셨다면, 이번 글로 그 퍼즐을 조금 더 맞출 수 있었길 바랍니다.
유용한 링크 & 리소스
- OpenCL 공식 홈페이지 : 플랫폼/디바이스 관리 관련 표준 문서
- NVIDIA CUDA Toolkit : CUDA 디바이스 초기화 비교 참조용
- Khronos Group GitHub : 다양한 OpenCL 샘플 코드
- Intel OpenCL SDK : Intel GPU/CPU용 OpenCL 참고
- C++ Reference : 최신 C++ 문법 및 라이브러리 레퍼런스
'개발 이야기 > OpenCL' 카테고리의 다른 글
[OpenCL 입문 시리즈 6편] 간단한 이미지 처리 예제: 그레이스케일 변환하기 (0) | 2024.12.12 |
---|---|
[OpenCL 입문 시리즈 5편] C++20/23로 깔끔하게 래핑하기 (0) | 2024.12.11 |
[OpenCL 입문 시리즈 4편] 메모리 관리 기초: 버퍼(Buffer), 이미지(Image), 파라미터(Argument) 다루기 (0) | 2024.12.10 |
[OpenCL 입문 시리즈 3편] 커널 작성 & 빌드: 기본 문법 따라하기 (1) | 2024.12.09 |
[OpenCL 입문 시리즈 1편] 개발환경 준비 & 첫 번째 "Hello OpenCL!" (0) | 2024.12.08 |