9년 동안 University of Barcelona 에서 OpenCL/CUDA 를 가르치다가 산업으로 넘어간 Oscar Amoros Huguet 가 — “CUDA 닌자가 아닌 동료 엔지니어가 GPU 성능을 누리게 하려면 코드를 어떻게 짜야 하는가” 를 두 가지 실제 사례로 깐다. 이 강의는 단일 커널 최적화가 아니라 호스트 측 아키텍처, vertical/horizontal fusion, graph 기반 파이프라이닝, delay buffer 같은 시스템 패턴이 중심이다.
Oscar 의 출발점은 단순하다 — 회사에서 그는 CUDA 를 아는 유일한 엔지니어였고, 동료 비전·미디어 엔지니어들에게 CUDA 를 가르치려고 9년 가까운 노력을 쏟았는데 성과는 미미했다. 그래서 방향을 뒤집었다 — “이 사람들이 CUDA 를 모르더라도, 내가 만든 라이브러리를 부르기만 하면 좋은 성능을 얻게 한다.”
강의의 두 사례는 둘 다 같은 메시지를 다른 도메인에서 보여준다.
대부분의 GPU Mode 강의가 “커널을 어떻게 빠르게 짜는가” 를 다룬다면 — 이 강의는 그 한 단계 위, “여러 커널을 어떻게 묶어 production 라이브러리로 출하하는가” 를 다룬다. 단일 SM 위 warp scheduling 보다 호스트 측 그래프, iteration boundary, API 표면적 이 더 자주 등장한다.
그래서 이 강의의 끝에 손에 잡혀야 하는 건 단순한 “fast kernel” 가 아니라 — 4가지 라이브러리 패턴이다: vertical fusion, horizontal fusion, producer-consumer streams, delay-buffered graph iteration.
Oscar 가 강의 초반에 못 박는 진단 — 회사에서 새 GPU 코드 컨설팅 문의가 들어왔을 때 가장 먼저 들여다보는 건 커널이 아니라 호스트 코드다. 왜냐하면 대부분의 application 에서 GPU 는 잘 도는데 — 호스트가 GPU 를 제대로 먹이지 못하기 때문에 비어 있다.
호스트 측에서 흔히 보이는 패턴들 — Oscar 의 진단 체크리스트.
새 application 을 받으면 먼저 nsys 로 system-level timeline 을 본다. 커널이 비는 갭이 있는지, memcpy 가 길게 박혀 있는지, host API 호출이 GPU 작업과 어떤 패턴으로 겹치는지. 그 갭을 어떻게 메울지 결정한 뒤에야 NCU 로 단일 커널을 들여다본다.
“GPU utilization” 만 봐서는 안 된다. nvidia-smi 의 SM utilization 이 80% 라도, 그 80% 가 20개의 작은 커널들이 매번 launch overhead 로 채워서 만들어진 숫자라면 진짜 일은 5%만 한 것일 수 있다. system trace 로만 잡힌다.
Oscar 는 이 진단을 바탕으로 — 라이브러리를 설계할 때 “호스트가 단순한 호출 한 줄로 GPU 위에 큰 일감을 던지게 하는 것” 이 가장 중요한 디자인 결정이라고 본다. 그래서 다음 섹션의 추상화 층 분리가 의미를 갖는다.
Oscar 의 핵심 디자인 원칙 — 라이브러리는 같은 코드를 세 가지 시각으로 동시에 만족시켜야 한다. 각 층은 서로 다른 추상화 수준으로 같은 기능을 노출한다.
fk::resize_normalize_crop(in, out, params) 같은 한 줄짜리 호출만 본다. stream 도 자기가 관리할 필요 없도록 default 가 합리적으로 잡혀 있어야 한다. 이 층이 가장 큰 사용자층이다.이 층 분리가 실제 코드에 어떻게 반영되는지 — Oscar 의 FK 라이브러리는 op 를 lambda 로 표현하고 fusion graph 를 템플릿 합성으로 만든다. L1 author 는 fk::compose(resize, normalize, crop) 같은 식으로 새 파이프라인을 만들 수 있고, L2 ninja 가 lambda 본문을 더 잘 만들면 L0 user 의 코드는 그대로 두고도 빨라진다.
그리고 이 디자인의 실용적인 장점 — 새로운 동료가 회사에 들어왔을 때, 그 사람이 어떤 layer 에서 일을 시작할지를 명시적으로 정할 수 있다. 모두를 닌자로 만들 필요가 없다.
Memory-bound op 들의 가장 흔한 낭비 — resize 가 결과를 HBM 에 쓰고, 다음 op 인 normalize 가 그걸 다시 읽고, 또 쓰고, 다음 crop 이 또 읽는다. 같은 픽셀이 HBM 사이를 여러 번 왕복한다. vertical fusion 은 이 chain 을 한 커널로 합쳐서 — 픽셀이 한 번 SRAM 에 올라오면 그 위에서 모든 op 가 도는 구조다.
vertical fusion 을 라이브러리에서 자동화하려면 — op 를 read fragment, compute fragment, write fragment 로 분해하고, 합성할 때 read/write 는 양 끝에서 한 번만, compute 는 chain 으로 이어붙이는 구조가 필요하다. Oscar 의 FK 가 정확히 이 모델.
이게 compiler-driven fusion(torch.compile 의 Inductor 가 하는 것) 과 다른 점은 — compiler 는 사용자 코드의 의미를 파싱해야 하지만, 라이브러리는 op 의 모양을 미리 알기 때문에 항상 안전하게 합칠 수 있다는 것. 두 접근이 상호보완.
// FK 스타일 fusion (의사코드)
auto pipeline = fk::compose(
fk::read(input),
fk::resize(out_w, out_h),
fk::normalize(mean, std),
fk::crop(box),
fk::write(output)
);
// 한 번의 launch 로 SRAM 위에서 모두 합성
pipeline.launch(stream);
Vertical fusion 이 “한 픽셀이 여러 stage 를 통과한다” 면, horizontal fusion 은 “한 입력에서 여러 출력이 동시에 나간다”. 비전 파이프라인에서 흔하다 — 같은 frame 에서 작은 thumbnail, 큰 inference 입력, 시각화용 overlay 가 동시에 필요한 경우.
실제 라이브러리에서는 둘이 섞인다. resize → normalize 의 vertical chain 끝에서 동시에 thumbnail 과 inference 입력으로 분기하는 식. Oscar 의 FK 는 op 합성 시 tree 를 받을 수 있게 설계되어 있어 둘을 같이 표현한다.
이 패턴이 가장 많이 만나는 자리는 — multi-resolution / multi-output 모델 추론 파이프라인. 같은 이미지에서 detector 와 classifier 가 다른 해상도를 요구하는데, 각 모델이 자기 전처리를 따로 부르면 H2D copy 가 두 배가 된다.
Vertical/horizontal fusion 으로 단일 iteration 안의 HBM 왕복을 줄였다면, 다음 도구는 iteration 사이의 latency를 줄이는 것. CUDA streams 와 producer-consumer 패턴이 표준 도구다.
이 패턴을 라이브러리에 넣을 때 주의할 함정 두 가지.
cudaMemcpyAsync 는 host buffer 가 pinned (cudaMallocHost) 일 때만 진짜로 비동기다. 일반 malloc 으로 잡힌 host 메모리에서는 동기로 떨어진다.cudaMemcpy(non-async) 한 번 부르면 모든 stream 이 그 시점에서 sync 된다. legacy default stream 모델. --default-stream per-thread 로 빌드하면 완화.// 두 stream — pinned host buffer + async copy
cudaStream_t copy_s, compute_s;
cudaStreamCreate(©_s);
cudaStreamCreate(&compute_s);
// host pinned buffer 풀
float* h[N];
for(int i=0; i<N; ++i)
cudaMallocHost(&h[i], bytes);
for(int i=0; i<num_iters; ++i){
// 다음 입력을 copy stream 으로 미리
cudaMemcpyAsync(d_in[i+1], h[i+1], bytes,
cudaMemcpyHostToDevice, copy_s);
// 현재 입력으로 compute stream 위 kernel
kernel<<<..., compute_s>>>(d_in[i], d_out[i]);
}
Oscar 가 강의 후반부에 가장 길게 깐 패턴 — delay buffer. iteration 사이에 작업이 의존성을 가질 때, 매번 sync 하면 파이프라인이 멈춘다. 그런데 어떤 종류의 데이터는 몇 step 늦어도 결과가 같다 — 그 자리에 delay buffer 를 박아 sync 자체를 우회한다.
delay buffer 패턴이 적용 가능한 조건.
이 조건이 맞으면 — 같은 그래프가 매 iteration 시작 시 pointer 만 swap 하고 sync 없이 그냥 도는 형태가 된다. Oscar 가 강의에서 비디오 파이프라인의 monitoring/feedback loop 에 이걸 적용한 예를 든다.
이 패턴은 CUDA Graphs API(cudaGraph) 와 자연스럽게 어울린다. 한 iteration 의 모든 작업을 graph 로 capture 한 뒤 cudaGraphInstantiate 로 instance 를 만들고, cudaGraphLaunch 로 호출한다. host 측 launch overhead 가 N→1 로 떨어지면서 delay buffer 의 효과가 더 커진다.
delay buffer 가 잘 박혔는지는 nsys timeline 에서 “iteration 사이에 GPU 가 비는 갭이 있는가” 를 보면 된다. 갭이 사라졌다면 host sync 가 진짜로 빠졌다는 뜻.
강의의 첫 사례. Oscar 의 회사에서는 OpenCV 의 GPU 모듈을 쓴다 — 그런데 OpenCV 는 op 단위로 host API 를 노출하기 때문에 여러 op 를 묶어 부르면 자연스럽게 vertical fusion 기회를 놓친다. FK (FastKAT) 가 그 위에 얹히는 layer.
같은 비전 파이프라인 (resize → normalize → crop → ToTensor) 을 — (1) OpenCV GPU op 4개로 직렬 호출, (2) FK 합성으로 한 커널로 fuse — 두 방식으로 측정. memory-bound 한 op 들이라서 fused 버전이 약 3-4× 빠르다. 같은 GPU, 같은 입력. 차이는 HBM 왕복 횟수에서 온다. (정확한 수치는 강의 슬라이드 직접 확인 필요 — 본 노트는 패턴만 재구성.)
이 사례에서 반복되는 디자인 결정 — “user 가 손을 한 번도 안 대게 한다”. FK 가 도입되기 전과 후의 user 코드 한 줄도 안 바뀐다. 라이브러리가 위에서 자동으로 fusion graph 를 만든다. 새 user 가 회사에 들어와도 OpenCV 만 알면 된다.
강의에서 짧지만 인상적으로 다룬 부분 — Oscar 가 회사의 일부 product 를 Windows 환경에 배포하면서 만난 시스템 차이. 같은 GPU, 같은 코드인데 Windows 에서 더 느리고 더 비결정적이다.
주된 원인은 Windows 의 WDDM (Windows Display Driver Model). WDDM 은 GPU 자원을 OS 가 시간분할로 관리하기 때문에, application 이 stream 을 자기가 가진 것처럼 직접 제어하지 못한다. 결과:
NVIDIA 가 제공하는 TCC (Tesla Compute Cluster) driver 모드가 있긴 하지만 — display 출력이 없는 GPU 에서만 가능하고 GeForce 카드에선 못 켠다. 즉 production application 의 deployment target 이 Windows 인지 Linux 인지가 — 라이브러리 디자인 단계부터 영향을 준다.
Oscar 의 권고 — “performance critical application 은 Linux 에서 돌려라”. Windows 에서 도는 게 deployment 요건이라면 — (1) launch 횟수를 더 공격적으로 줄여라 (vertical fusion 더 강하게), (2) CUDA Graphs 로 graph capture 후 한 번에 launch, (3) 측정을 Windows 에서 직접 한다.
이 자리는 강의에서 가장 짧게 다뤄지지만 — 실제 production deployment 에서는 가장 자주 만나는 함정 중 하나. NVIDIA 의 driver mode 문서와 CUDA 의 cudaDeviceGetAttribute(cudaDevAttrComputeMode) 가 첫 진입점.
이 강의의 가치는 단일 커널 최적화가 아니라 — 여러 커널을 묶어 production 라이브러리로 만드는 패턴 카탈로그다. 다음 라이브러리 디자인을 시작할 때 빠르게 복원해야 하는 핵심.
Resize → Normalize → CenterCrop 전처리 chain 을 (1) 단계별 따로 호출, (2) torch.compile 로 compose, (3) 직접 Triton 으로 fuse. nsys 로 커널 수와 H2D 트래픽 비교.cudaMemcpyAsync + 두 stream 으로 image preprocessing 파이프라인을 깐다. 단일 stream 대비 throughput 측정.L010 의 “라이브러리 디자인” 시각이 다른 강의의 단일 도구들과 어떻게 맞물리는지.
이 강의는 산업 사례 위주라서 — 본문 안의 구체적 수치와 코드 디테일이 슬라이드/repo 에 흩어져 있다. 다음 회독 때 채울 자리들.
fk::compose(...) 같은 의사코드 수준으로만 적었다. morousg 의 실제 repo 확인 필요. 확인 필요.이 노트의 거의 모든 수치/예시는 강의 패턴을 재구성한 형태이며, 정확한 측정값과 API 시그니처는 Oscar 의 슬라이드와 morousg 의 repo 를 직접 봐야 한다. 다음 회독에서 채워 넣기.