OpenCL/GPU 큰 그림 정리 (Bullet Wiki)
이 문서는 대화에서 다룬 개념을 한 번에 연결해 보는 개인 위키 요약본이다. 설명은 “큰 그림 -> 실행 흐름 -> 병렬 모델 -> 성능/디버깅 포인트” 순서로 구성한다.
0) 한 줄 큰 그림
- GPU 실행은 대체로 다음 순서로 이해하면 된다:
- 미리 규칙(인터페이스 계약)을 정한다
- 실행 직전 실제 리소스를 그 규칙 슬롯에 꽂는다
- dispatch로 대량 work-item을 시작한다
1) 물류 비유로 핵심 개념 대응
- 박스 하나 = 리소스 하나 (
VkBuffer,VkImageView등) - 트럭 = Descriptor Set
- 트럭 번호 = set index
- 트럭 칸 번호 = binding index
- 칸 규격(무슨 박스 타입/몇 개 가능) = descriptor type/count
- 규격서(오래 쓰는 문서) = Descriptor Set Layout (DSL)
- 센터 전체 계약서 = Pipeline Layout (set layouts + push constants)
- 오늘 작업표(실행마다 바뀜) = descriptor write/update 결과
- 작업 라인 선택 = pipeline bind
- 대량 처리 시작 버튼 = dispatch
- 맨 아래층 기계 제어 지시 흐름 = driver backend command stream / PM4 인접 레벨
2) 정적 계약 vs 동적 값
- 정적(보통 미리 고정)
- set index
- binding index
- descriptor type
- descriptor count
- stage visibility
- push constant ranges
- 동적(실행마다 바뀔 수 있음)
- 실제 리소스 핸들(오늘 넣는 buffer/image)
- offset/range(버퍼 일부 사용 범위)
- 일부 push constant 값
- 기억 문장:
- 슬롯 규칙은 정적, 슬롯 내용물은 동적
3) 왜 pipeline 생성 성공 후 runtime 실패가 가능한가
- create 단계 성공:
- 셰이더 + pipeline layout 자체가 논리적으로 일관됨
- runtime 실패:
- 실제 bind한 descriptor set/리소스가 계약과 불일치
- 비유:
- 설계도 승인 완료
- 현장 조립 때 규격 다른 부품을 꽂아 실패
4) clSetKernelArg 위치 감각
- 실전 감각상
clSetKernelArg는- **코드 생성(compile chain)**보다는
- **실행 전 상태 준비(submit 쪽 준비)**에 가깝게 보는 것이 유용
- 이유:
- 빌드 산출물 자체를 바꾸기보다
- 이번 실행에서 사용할 인자/리소스 상태를 채우는 성격
5) compile chain vs submit chain
- compile chain
- OpenCL C 전처리/컴파일/IR 변환/코드 생성 경로
- 산출물: 실행 가능한 코드/모듈/내부 바이너리
- submit chain
- 커맨드 기록, 바인딩, dispatch, 동기화, 제출 경로
- 산출물: 실제 실행 작업 흐름
- 분리 이점:
- 실패 원인 분리(빌드 vs 바인딩/런타임)
- 성능 원인 분리(초기 컴파일/JIT vs 반복 제출 오버헤드)
6) dispatch는 “트럭 수"가 아니라 “작업자 수”
- dispatch는 물류 비유에서 “작업자 호출"에 더 가깝다
- 예: global size = 1,000,000
- 논리적으로 work-item 100만 개가 같은 커널 코드를 실행
- 중요:
- 코드는 같고
- 각 work-item의 인덱스(id)가 달라 처리 데이터가 달라짐
7) 모든 work-item이 같은 레이아웃을 보나?
- 같은 dispatch 내에서는 개념적으로
- 같은 pipeline
- 같은 pipeline layout/DSL 계약
- 같은 descriptor set 바인딩 맥락 아래에서 실행
- 단, 각 work-item은 자신의 id로 서로 다른 데이터 위치를 접근
8) 왜 고정 슬롯 규칙이 빠른가
- 런타임 추론(매번 타입/위치 확인) 비용을 줄인다
- 주소/인덱스 경로가 단순하고 예측 가능해진다
- 검증/호환성 체크를 create/bind 쪽으로 앞당길 수 있다
- 드라이버 하부 명령 구성(backend/PM4 인접)도 정형화되기 쉽다
- 결론:
- 유연성 일부를 포기해 처리량/예측 가능성을 얻는 구조
9) OpenCL arg -> 슬롯 매핑 감각
- 커널 인자 시그니처는 리소스 인터페이스의 출발점
- clspv/SPIR-V 경로에서 set/binding 표현으로 나타남
- host 쪽 DSL/PL은 그 표현과 호환되어야 함
- 실행 시 descriptor write로 실제 리소스를 슬롯에 꽂음
- arg0 하나만 따라가도 전체 경로를 이해하기 좋음
10) first-dispatch가 느릴 수 있는 대표 원인
- pipeline 생성/캐시 미스
- driver backend JIT/내부 컴파일
- 첫 제출 경로의 초기화 오버헤드
- 완화 전략:
- 파이프라인/캐시 워밍업
- 불필요한 초기화 지연 제거
- 제출 배치 최적화
11) tiny frequent dispatch에서 CPU 병목 완화
- command buffer recording 재사용
- submit 횟수 줄이기(배치화)
- 불필요한 동기화 축소
- descriptor/pipeline churn 감소
- 목표:
- CPU 제출 오버헤드 감소
- 하부 명령 스트림 구성 빈도 완화
12) 자주 헷갈리는 포인트 정리
- “set/binding이 실행 때 막 바뀌나?”
- 보통 인터페이스 계약은 정적, 내용물만 동적
- “pipeline 성공이면 다 성공 아닌가?”
- runtime bind 불일치로 실패 가능
- “모든 work-item이 똑같이 일하면 결과도 같지 않나?”
- 코드만 같고, id가 달라 각자 다른 데이터 처리
- “PM4는 박스냐 트럭이냐?”
- 둘 다 아님. 하부 실행 제어 지시 포맷에 가까움
13) 대화 외 추가 개념 (이해 확장용)
- Command Buffer
- GPU에게 시킬 일을 미리 기록한 명령 리스트
- 한 번 기록해 재사용하면 CPU 비용 절감
- Queue
- 기록된 명령을 실제 제출하는 줄(작업 대기열)
- Fence/Semaphore
- CPU-GPU, GPU-GPU 간 완료 순서를 맞추는 동기화 도구
- Push Constants
- 작은 상수 데이터를 빠르게 전달하는 경량 경로
- Descriptor Pool
- descriptor set을 할당하는 메모리 풀
- Pipeline Cache
- 파이프라인 생성 비용을 줄이기 위한 캐시
- Workgroup(Local size)
- work-item을 묶는 단위. 성능 튜닝에서 매우 중요
- Occupancy (점유율)
- GPU 연산 자원이 얼마나 채워져 실행되는지의 감각 지표
- Memory Coalescing
- 인접한 메모리 접근을 묶어 효율을 높이는 패턴
- Barrier/Sync in kernel
- workgroup 내부 작업 순서를 맞춰 데이터 경쟁을 방지
14) 학습 루트 추천 (짧은 로드맵)
- 1단계: set/binding/DSL/PL 용어 고정
- 2단계: dispatch + work-item/workgroup 실행 모델 이해
- 3단계: command buffer/queue/sync로 submit path 이해
- 4단계: first-run latency와 tiny-dispatch 병목 튜닝
- 5단계: backend command stream/PM4 관점으로 아래층 감각 확장
15) 30초 복습 카드
- 규칙은 미리 고정, 실물은 실행 때 꽂기
- dispatch는 작업자 수를 호출하는 버튼
- 코드 동일 + id 다름 = 데이터 병렬
- pipeline create 성공 != runtime 성공 보장
- 성능은 “재사용, 배치화, 동기화 절제"가 기본