본문 바로가기

AI Compiler framework

AICF Python Binding - Plan A (Unified op_call) 설계 문서

1. 바뀐 구조의 확인

이전 방식 ( op 별 pyd 작성 )

  • add.pyd, relu.pyd, gemm.pyd 처럼 op 마다 개별 pybind 모듈
  • Python 쪽에서
  • import aicf_cuda.add, import aicf_cuda.relu 와 같은 방식

문제점

  • op가 늘어날수록
    • CMake 타겟 증가
    • pyd 파일 증가
    • Python import 경로 복잡화
  • 프레임 워크 관점에서 IR - op - kernel dispatch 흐름이 분산됨
  • graph / planner / runtime 과의 통합이 어려움

 

2. Plan A 핵심 아이디어

CUDA 확장 모듈

aicf_cuda/
 ├─ __init__.py
 └─ _C.pyd      ← 모든 CUDA op의 단일 진입점
  • pybind 모듈 이름 : _C
  • Python 패키지 이름 : aicf_cuda
  • Python 에서 사용하는 API
import aicf_cuda as aicf

aicf.op_call(
    aicf.OpKind.EltwiseAdd,
    inputs=[a, b],
    outputs=[out],
    attrs={}
)

 

3. 전체 호출 흐름 ( End - to - End )

Python
  ↓
aicf_cuda.op_call()
  ↓
bindings.cpp
  - Tensor → TensorDesc
  - dict → AttrPack
  ↓
dispatch_v0()
  ↓
KernelRegistry
  ↓
KernelVariant::supported()
  ↓
KernelVariant::launch()
  ↓
CUDA kernel

핵심 포인트

  • Python 은 op 이름이나 커널을 모른다
  • 오직 OpKind + TensorDesc + AttrPack 만 전달
  • 커널 선택은 전부 C++ 레지스트리 내부 책임

 

4. 현재 관리해야 하는 중앙 축

(1) OpKind - 연산의 ID

enum class OpKind : int {
  EltwiseAdd = 0,
  EltwiseRelu = 1,
  Gemm = 2,
  _Count
};
  • 새 op 추가 시 반드시 여기 추가
  • Python 에서도 동일 enum 을 그대로 사용

 

(2) KernelVariant - 커널의 제약

struct KernelVariant {
  const char* name;

  Status (*launch)(...);
  bool   (*supported)(...);
  size_t (*query_workspace)(...);
};
  • 하나의 OpKind 는 여러 KernelVariant 를 가질 수 있음
    • gemm_f32_naive,  gemm_f16_tc, gemm_f32_tiled
  • supported() 가 런타임 커널 선택 로직

 

(3) KernelRegistry - 전역 커널 테이블

R.register_kernel(Opkind::Gemm, make_gemm_f32_naive_variant());
  • register_all.cpp 가 유일한 등록 지점
  • Python 에서 import 시 딱 한 번 호출

 

(4) bindings.cpp - Python <-> C++ 경계

  • Tensor 검증
  • TensorDesc 생성
  • AttrPack 생성
  • dispatch_v0() 호출

bindings.cpp 에는

  • kernel 코드 x
  • op 분기 x
  • shape inference x

 

5. 새 op 추가 시 해야 할 일 

1단계 : OpKind 추가

enum class OpKind : int {
  ...
  Conv2d = 3,
};

 

2단계 : 커널 구현

src/backends/cuda/ops/conv2d/
 ├─ kernels.cuh
 └─ launcher.cu
  • launcher.cu 안에서
    • public API
    • KernelVariant make_conv2d_f32_variant()

 

3 단계 : KernelVariant 정의

KernelVariant make_conv2d_f32_variant() {
  KernelVariant v;
  v.name = "conv2d_f32_naive";
  v.supported = conv2d_supported;
  v.launch = conv2d_launch;
  v.query_workspace = conv2d_workspace;
  return v;
}

 

4 단계 : register_all.cpp 에 등록

R.register_kernel(OpKind::Conv2d, make_conv2d_f32_variant());

 

할 필요 없는 것들

  • 새 pybind 파일 추가
  • CMake 에 pyd 타겟 추가
  • Python import 수정
  • bindings.cpp 수정 ( 대부분의 경우 )

 

6. 테스트 코드 변화

이전

import aicf_cuda.add
aicf_cuda.add.add_f32(...)

현재 

import aicf_cuda as aicf

aicf.op_call(
    aicf.OpKind.EltwiseAdd,
    [a, b],
    [out],
    {}
)

장점

  • 테스트 코드가 IR / graph executor 테스트 형태와 동일
  • 프레임워크 테스트와 커널 테스트의 경계가 사라짐

 

7. 이 구조의 장기적 의미

  • Python -> C++ -> CUDA 가 완전히 IR 중심 구조
  • op 증가는 레지스트리 데이터 증가일 뿐
  • graph capture / planner / fusino 로직이
    • Python API 변경 없이
    • kernel 레벨에서 진화 가능

이 구조는 이미 TVM / XLA / TorchInductor / TensorRT 스타일의 진입점과 동일