Articlehuggingface.co·2025년 8월 18일·0

From Zero to GPU: A Guide to Building and Scaling Production-Ready CUDA Kernels

Quick Summary

이 글은 RGB 이미지를 회색조로 변환하는 CUDA 커널을 예제로 삼아 구현, PyTorch 네이티브 연산자 등록, 재현 가능한 빌드, 다중 환경 컴파일, Hugging Face Hub 배포까지의 전체 과정을 설명한다.

From Zero to GPU: A Guide to Building and Scaling Production-Ready CUDA Kernels 관련 대표 이미지

🖼️ 인포그래픽

From Zero to GPU: A Guide to Building and Scaling Production-Ready CUDA Kernels 내용을 설명하는 본문 이미지

🖼️ 4컷 인포그래픽

From Zero to GPU: A Guide to Building and Scaling Production-Ready CUDA Kernels 내용을 설명하는 본문 이미지

💡 한 줄 요약

이 글은 RGB 이미지를 회색조로 변환하는 CUDA 커널을 예제로 삼아 구현, PyTorch 네이티브 연산자 등록, 재현 가능한 빌드, 다중 환경 컴파일, Hugging Face Hub 배포까지의 전체 과정을 설명한다.

📌 핵심 요약

  • kernel-builder는 로컬에서 개발한 사용자 정의 CUDA 커널을 여러 PyTorch·CUDA 환경용으로 빌드하고, 다른 개발자가 kernels 라이브러리를 통해 내려받아 사용할 수 있도록 배포하는 과정을 지원한다.
  • 예제 프로젝트는 build.toml, CUDA 소스 디렉터리, flake.nix, PyTorch 바인딩 코드, 파이썬 래퍼를 명확히 분리해 빌드 설정과 구현 및 사용자 인터페이스의 책임을 구분한다.
  • RGB 회색조 변환 커널은 2차원 스레드 격자에서 각 스레드가 하나의 픽셀을 처리하며, RGB 채널에 0.21, 0.72, 0.07의 가중치를 적용해 회색조 값을 계산한다.
  • 커널을 PyTorch 네이티브 연산자로 등록하면 torch.ops에서 호출할 수 있고, torch.compile이 연산자를 인식하며 입력 텐서의 장치에 따라 구현을 선택하는 디스패치 구조로 확장할 수 있다.
  • 개발 중에는 특정 PyTorch·CUDA 조합의 Nix 셸에서 빠르게 반복 빌드하고, 배포 단계에서는 전체 지원 조합을 빌드한 뒤 결과물을 build 디렉터리에 배치해 Hugging Face Hub에 업로드한다.

🧩 주요 포인트

  1. kernel-builder는 로컬에서 개발한 사용자 정의 CUDA 커널을 여러 PyTorch·CUDA 환경용으로 빌드하고, 다른 개발자가 kernels 라이브러리를 통해 내려받아 사용할 수 있도록 배포하는 과정을 지원한다.
  2. 예제 프로젝트는 build.toml, CUDA 소스 디렉터리, flake.nix, PyTorch 바인딩 코드, 파이썬 래퍼를 명확히 분리해 빌드 설정과 구현 및 사용자 인터페이스의 책임을 구분한다.
  3. RGB 회색조 변환 커널은 2차원 스레드 격자에서 각 스레드가 하나의 픽셀을 처리하며, RGB 채널에 0.21, 0.72, 0.07의 가중치를 적용해 회색조 값을 계산한다.
  4. 커널을 PyTorch 네이티브 연산자로 등록하면 torch.ops에서 호출할 수 있고, torch.compile이 연산자를 인식하며 입력 텐서의 장치에 따라 구현을 선택하는 디스패치 구조로 확장할 수 있다.
  5. 개발 중에는 특정 PyTorch·CUDA 조합의 Nix 셸에서 빠르게 반복 빌드하고, 배포 단계에서는 전체 지원 조합을 빌드한 뒤 결과물을 build 디렉터리에 배치해 Hugging Face Hub에 업로드한다.

🧠 상세 정리

1. 사용자 정의 CUDA 커널을 제품 수준으로 만드는 목표

이 글은 단순한 GPU 함수를 작성하는 데서 끝나지 않고, 실제 사용자가 안정적으로 받아 실행할 수 있는 CUDA 커널 패키지를 만드는 과정을 다룬다. 저자들이 만든 kernel-builder는 개발자가 로컬에서 커널을 구현한 뒤 여러 하드웨어 및 소프트웨어 조합용으로 빌드하고, 그 결과를 외부에 배포할 수 있도록 설계됐다. 최종 사용자는 kernels 라이브러리의 get_kernel 함수에 Hub 저장소 식별자를 전달해 커널을 내려받고, 반환된 모듈의 함수를 CUDA 텐서에 직접 적용할 수 있다. 글의 핵심 목표는 커널 자체의 속도뿐 아니라 재현 가능한 빌드, 넓은 호환성, 반복 가능한 개발 과정, 유지 가능한 배포 구조를 함께 확보하는 것이다.

2. 예측 가능한 프로젝트 디렉터리 구성

예제로 사용하는 img2gray 프로젝트는 빌드 설정과 CUDA 구현, PyTorch 연결 코드, 파이썬 인터페이스를 서로 다른 위치에 배치한다. 루트의 build.toml은 전체 빌드를 지휘하는 매니페스트이며, csrc/img2gray.cu에는 GPU에서 실행될 실제 CUDA 코드가 들어간다. flake.nix는 동일한 도구와 의존성으로 빌드할 수 있는 환경을 정의해 재현성을 담당한다. torch-ext에는 torch_binding.cpp와 torch_binding.h가 위치해 CUDA 함수를 PyTorch 연산자로 연결하며, torch-ext/img2gray/init.py는 사용자가 호출할 파이썬 래퍼를 제공한다. 이러한 구성은 각 파일의 책임을 분명히 하고 kernel-builder가 필요한 소스와 패키지 구조를 일관된 방식으로 찾을 수 있게 한다.

3. build.toml과 flake.nix를 통한 빌드 정의

build.toml의 general 영역은 프로젝트 이름을 img2gray로 지정하고 universal 값을 false로 설정한다. torch 영역에는 PyTorch 바인딩에 사용되는 torch_binding.cpp와 torch_binding.h가 등록되며, kernel.img2gray 영역은 백엔드를 CUDA로 지정하고 Torch 의존성을 선언한 뒤 csrc/img2gray.cu를 커널 소스로 연결한다. 이 의존성은 CUDA 구현에서 torch::Tensor를 사용할 수 있도록 하는 역할을 한다. flake.nix는 Hugging Face kernels 저장소의 kernel-builder를 입력으로 고정하고, genFlakeOutputs에 현재 프로젝트 경로와 리비전 정보를 전달해 빌드 출력을 생성한다. 글은 이 구성이 정확한 kernel-builder 및 관련 의존성 버전을 잠가 서로 다른 컴퓨터에서도 동일한 환경을 재현하고, 이른바 ‘내 컴퓨터에서는 된다’는 문제를 줄인다고 설명한다.

4. 2차원 스레드 격자로 구현한 RGB 회색조 변환

img2gray_kernel은 입력과 출력의 uint8_t 포인터, 이미지 너비와 높이를 받아 GPU에서 실행되며, 각 스레드는 하나의 픽셀을 담당한다. 스레드의 x와 y 좌표는 블록 및 스레드 인덱스로 계산하고, 이미지 경계 안에 있는 경우에만 해당 픽셀을 처리한다. 입력은 픽셀마다 RGB 세 채널이 연속 배치된 형식으로 다뤄지며, 각 채널에 0.21, 0.72, 0.07을 곱한 합을 uint8_t 회색조 값으로 변환한다. 호스트 측 img2gray_cuda 함수는 입력 텐서의 두 번째 차원에서 너비를, 첫 번째 차원에서 높이를 읽고 16×16 크기의 블록을 정의한다. 이미지 전체를 덮도록 각 축의 그리드 크기를 올림 방식으로 계산한 뒤 입력과 출력 텐서의 데이터 포인터를 전달해 CUDA 커널을 실행한다.

5. PyTorch 네이티브 연산자 등록과 확장성

저자들은 단순한 파이썬 바인딩 대신 img2gray 함수를 PyTorch의 네이티브 연산자로 등록하는 단계를 가장 중요한 부분으로 제시한다. torch_binding.cpp의 TORCH_LIBRARY_EXPAND 블록은 입력 텐서와 변경 가능한 출력 텐서를 받는 img2gray 연산자 스키마를 정의하고, CUDA 디스패치 키에 img2gray_cuda 구현을 연결한다. REGISTER_EXTENSION은 해당 확장을 등록하며, 완성된 연산자는 torch.ops 계열의 네임스페이스에서 접근할 수 있다. 이 등록 방식 덕분에 torch.compile이 사용자 정의 연산자를 인식해 더 큰 계산 그래프 안에서 다룰 수 있으며, 글은 이를 연산 오버헤드를 줄이고 PyTorch의 성능 생태계와 통합하는 핵심으로 설명한다. 또한 같은 연산자에 CPU용 C++ 구현 등을 추가하면 PyTorch 디스패처가 입력 텐서의 장치에 따라 CUDA 또는 CPU 구현을 자동으로 선택하는 구조로 확장할 수 있다.

6. 파이썬 래퍼와 반복 가능한 로컬 빌드

torch-ext/img2gray/init.py는 kernel-builder가 템플릿에서 자동 생성한 _ops 모듈의 ops를 가져와 사용자 친화적인 img2gray 함수를 노출한다. 래퍼는 입력 텐서의 높이, 너비, 채널 수를 읽고 채널이 세 개인지 확인한 다음, 입력과 동일한 장치 및 자료형을 사용하는 높이×너비 출력 텐서를 만든다. 이후 ops.img2gray에 입력과 출력을 전달하고 계산이 끝난 출력 텐서를 반환한다. 빌드 과정에서는 원하는 PyTorch, C++ ABI, CUDA 버전 및 시스템 아키텍처에 대응하는 Nix 개발 셸을 선택할 수 있으며, 글은 PyTorch 2.7과 CUDA 12.6 조합을 예로 든다. build2cmake generate-torch 명령은 CMakeLists.txt, pyproject.toml, setup.py, 등록 헤더, 자동 생성 연산자 모듈과 CMake 보조 파일을 만든다. 마지막으로 파이썬 가상환경을 생성하고 pip의 편집 가능 설치를 실행하면 소스 수정과 재컴파일을 반복할 수 있는 개발 환경이 완성된다.

7. 실제 이미지로 확인하는 개발 주기

글은 커널 등록과 실행 결과를 확인하기 위한 간단한 sanity.py 테스트를 제시한다. 테스트는 PIL로 컬러 이미지를 RGB 형식으로 열고 NumPy 배열로 변환한 뒤, torch.from_numpy로 텐서를 만들고 CUDA 장치로 이동한다. 이 입력은 높이·너비·채널 구조를 가지며, img2gray.img2gray를 호출한 결과는 높이·너비의 회색조 텐서가 된다. 결과 텐서를 CPU로 옮겨 NumPy uint8 배열로 바꾸고, PIL의 회색조 모드 이미지로 생성해 파일로 저장함으로써 실제 출력을 확인한다. 문제가 발견되면 CUDA 소스나 바인딩 코드를 수정하고 이미 준비된 Nix 환경을 재사용해 다시 빌드하는 방식으로 개발 주기를 반복할 수 있다.

8. 전체 지원 조합 빌드와 Hub 배포

배포 전에는 build2cmake clean 명령으로 개발 중 생성된 불필요한 파일을 정리해 업로드 대상에 임시 산출물이 섞이지 않도록 한다. 로컬 개발에서는 특정 PyTorch·CUDA 조합만 선택했지만, 넓은 사용자층에 제공하려면 지원되는 모든 조합에서 빌드하고 실행할 수 있는 호환 커널이 필요하다. 개발 셸 밖에서 nix build를 실행하면 kernel-builder가 지원하는 PyTorch 및 CUDA 변형을 빌드하며, 결과는 result 디렉터리에 생성된다. 지원 변형은 최신 PyTorch·CUDA 릴리스와 더불어 호환성 확보를 위한 이전 버전도 포함하도록 관리된다고 설명한다. 생성된 결과는 kernels 라이브러리가 찾는 build 디렉터리로 rsync를 사용해 옮기며, 기존 파일과 권한도 배포에 맞게 정리한다. 마지막으로 kernels upload 명령에 커널 경로와 Hub 저장소 식별자를 전달해 빌드 산출물을 게시할 수 있고, 글은 표준 Git 기반 업로드 절차도 선택지로 제시한다.

🧾 핵심 주장 / 시사점

  • PyTorch 네이티브 연산자 등록은 CUDA 코드를 파이썬에서 호출하기 위한 연결 수단에 그치지 않고, torch.compile의 계산 그래프 인식과 장치별 구현 선택을 가능하게 하는 통합 지점이다.
  • 개발 단계에서 단일 PyTorch·CUDA 조합만 빠르게 빌드하고 배포 단계에서 전체 지원 조합을 생성하는 분리는 반복 개발의 효율성과 공개 배포의 호환성을 함께 확보한다.
  • 프로덕션 수준의 커널은 GPU 코드만으로 완성되지 않으며, 고정된 빌드 환경, 명시적인 매니페스트, 자동 생성 바인딩, 실제 입력 테스트, 정리된 다중 버전 산출물과 배포 경로가 하나의 체계로 구성돼야 한다.

✅ 액션 아이템

  • build.toml, CUDA 소스, flake.nix, PyTorch 바인딩, 파이썬 래퍼를 분리해 빌드 설정·구현·인터페이스 책임을 명확히 구분한다.
  • RGB 회색조 커널을 2차원 스레드 격자로 설계해 각 스레드가 한 픽셀을 처리하고 0.21·0.72·0.07 가중치로 회색조를 계산하도록 정리한다.
  • 커널을 torch.ops 네이티브 연산자로 등록해 torch.compile이 입력 텐서 장치별로 구현을 선택하는 디스패치 구조를 기준으로 적용한다.

❓ 열린 질문

  • torch.compile이 torch.ops 연산자를 인식한 뒤 CPU와 GPU에서 장치별 구현 분기가 기대 동작으로 일관되는지 어떤 검증 케이스를 둘 것인가?
  • Nix 셸에서 특정 PyTorch·CUDA 조합으로 반복 빌드한 결과를 기준으로, 전체 지원 조합 배포 전 어떤 비교 항목으로 안정성 여지를 줄일 것인가?
  • build 디렉터리 산출물을 Hugging Face Hub로 배포한 후 kernels 라이브러리 내려받기 흐름에서 최신 빌드가 정확히 매칭되는지 어떤 추적 방식이 적합한가?

관련 문서

공통 태그와 주제 흐름을 기준으로 같이 보면 좋은 문서를 이어서 제안합니다.