jayinlab

이 블로그의 콘텐츠는 AI가 작성·정리합니다.

OpenCL 큰 그림 — Platform에서 clFinish까지 한 번에 보기

2026-04-16

Vulkan 쪽 애니메이션(큰 그림 심화편)에서 descriptor set, pipeline layout을 봤다면,
이번에는 OpenCL로 같은 saxpy 커널을 실행하는 전 과정을 처음부터 끝까지 따라간다.

핵심 질문: OpenCL에서 GPU 버퍼를 커널에 연결하는 방법은 Vulkan과 무엇이 다른가?


📱 가로 화면이나 PC에서 더 잘 보입니다 — 터치로 장면 이동 가능

12장면 구성

1막 — 주문 이야기 (장면 1–9)

장면내용핵심 API
오늘 할 일 — saxpyy[i] = a × x[i] + y[i]
Platform & Device 선택clGetPlatformIDs, clGetDeviceIDs
Context & Command QueueclCreateContext, clCreateCommandQueue
버퍼 생성 + 데이터 복사clCreateBuffer, clEnqueueWriteBuffer
Program 컴파일clCreateProgramWithSource, clBuildProgram, clCreateKernel
커널 인자 연결 ★clSetKernelArg(k, 번호, 크기, &버퍼)
NDRange 설정global_size=1M, local_size=64 → 15,625 work-groups
실행 명령 등록clEnqueueNDRangeKernel
GPU 처리 + 결과 수령wavefront 64개 동시 실행, clEnqueueReadBuffer, clFinish

2막 — OpenCL 언어로 (장면 10–12)

장면내용핵심 포인트
API 이름으로 다시 보기배달 비유 → OpenCL 이름 레이블
OpenCL vs Vulkan 비교clSetKernelArg(직접) vs DSL→DS→bind(단계적)
전체 API 흐름 매핑비유 ↔ API ↔ 설명 3열 정리

핵심: 장면 ⑥ — clSetKernelArg

OpenCL 리소스 바인딩은 놀랍도록 간결하다.

// arg0 ← x 버퍼 (GPU 메모리)
clSetKernelArg(kernel, 0, sizeof(cl_mem), &x_buf);
// arg1 ← y 버퍼
clSetKernelArg(kernel, 1, sizeof(cl_mem), &y_buf);
// arg2 ← 결과 버퍼
clSetKernelArg(kernel, 2, sizeof(cl_mem), &out_buf);
// arg3 ← 스칼라 a
clSetKernelArg(kernel, 3, sizeof(float), &a);

Vulkan에서 같은 작업에 필요한 것:

vkCreateDescriptorSetLayout(...)   // 칸 규격서
vkAllocateDescriptorSets(...)      // 실제 트럭 생성
vkUpdateDescriptorSets(...)        // 박스 배정
vkCmdBindDescriptorSets(...)       // dispatch 전 바인딩

OpenCL = index 번호로 직접 꽂는다. Vulkan = 규격서-트럭-배정-바인딩 4단계.


장면 ⑦: NDRange가 work-item을 나누는 방식

size_t global_size = 1000000;   // 전체 work-item 수
size_t local_size  = 64;        // work-group 하나의 크기

// work-group 수 = 1,000,000 / 64 = 15,625

clEnqueueNDRangeKernel(
    q,            // command queue
    kernel,       // cl_kernel
    1,            // 1D
    NULL,         // global offset
    &global_size,
    &local_size,
    0, NULL, NULL // 이벤트 없음
);

커널 코드 안에서:

int i = get_global_id(0);  // 0 ~ 999,999
out[i] = a * x[i] + y[i];

모든 work-item이 자기 i 번호로 동시에 계산한다.


장면 ⑨: 전체 실행 흐름 (코드 요약)

전체 코드 보기 — saxpy_opencl.c (핵심 흐름)
// 1. Platform & Device
cl_platform_id platform;
cl_device_id device;
clGetPlatformIDs(1, &platform, NULL);
clGetDeviceIDs(platform, CL_DEVICE_TYPE_GPU, 1, &device, NULL);

// 2. Context & Queue
cl_context ctx = clCreateContext(0, 1, &device, NULL, NULL, &err);
cl_command_queue q = clCreateCommandQueueWithProperties(ctx, device, 0, &err);

// 3. Buffers
cl_mem x_buf = clCreateBuffer(ctx, CL_MEM_READ_ONLY|CL_MEM_COPY_HOST_PTR,
                               N*sizeof(float), host_x, &err);
cl_mem y_buf = clCreateBuffer(ctx, CL_MEM_READ_WRITE|CL_MEM_COPY_HOST_PTR,
                               N*sizeof(float), host_y, &err);
cl_mem out_buf = clCreateBuffer(ctx, CL_MEM_WRITE_ONLY,
                                N*sizeof(float), NULL, &err);

// 4. Program & Kernel
cl_program prog = clCreateProgramWithSource(ctx, 1, &src, &srcLen, &err);
clBuildProgram(prog, 1, &device, NULL, NULL, NULL);
cl_kernel k = clCreateKernel(prog, "saxpy", &err);

// 5. SetKernelArg (★ 핵심 — descriptor set 없이 직접!)
clSetKernelArg(k, 0, sizeof(cl_mem), &x_buf);
clSetKernelArg(k, 1, sizeof(cl_mem), &y_buf);
clSetKernelArg(k, 2, sizeof(cl_mem), &out_buf);
clSetKernelArg(k, 3, sizeof(float), &a);

// 6. Enqueue & Execute
size_t gsize = N, lsize = 64;
clEnqueueNDRangeKernel(q, k, 1, NULL, &gsize, &lsize, 0, NULL, NULL);

// 7. Read result
clEnqueueReadBuffer(q, out_buf, CL_FALSE, 0, N*sizeof(float), host_out, 0,NULL,NULL);
clFinish(q);

// 8. Cleanup
clReleaseMemObject(x_buf); clReleaseMemObject(y_buf); clReleaseMemObject(out_buf);
clReleaseKernel(k); clReleaseProgram(prog);
clReleaseCommandQueue(q); clReleaseContext(ctx);

OpenCL vs Vulkan 핵심 비교 3줄

1. OpenCL: clSetKernelArg(k, index, size, &buf) — index 번호로 직접 바인딩
2. Vulkan:  DSL → AllocDS → WriteDS → BindDS — 4단계 명시적 바인딩
3. OpenCL = 이식성·간결함 우선 / Vulkan = 낮은 수준 GPU 제어 우선

관련 글

관련 용어

[[work-item]], [[NDRange]], [[command-queue]], [[descriptor-set]], [[SPIR-V]], [[clspv]]