본문 바로가기

AI Compiler framework

CUDA Backend v2 (core-free) Ops 마이그레이션 규칙

0) 목표

  • C++은 단일 _C 모듈 + 단일 op_call 진입점
  • op별 파이썬 래퍼는 가볍게
  • 공통 규칙은 C++ registry/dispatch에서 강제
  • CUDA Graph 캡처 일관성(스트림/동적할당/전역상태)을 시스템적으로 확보

1) 바인딩/ABI 변화 (중요)

Before (구버전)

  • attrs를 dict/AttrPack으로 넘기거나,
  • core/runtime(shim/stream/status) 같은 공통 레이어에 의존

After (v2)

  • AttrBlob 기반 ABI 고정
    • (schema_id, bytes, data)만 C++로 전달
    • 파이썬은 struct.pack()으로 attrs 바이트를 만든다
  • _C.op_call(kind, inputs, outputs, schema_id, attrs_bytes, stream_handle)
    • stream_handle=0은 “현재 PyTorch 스트림” 사용(바인딩이 current stream 가져옴)
    • 캡처는 _C.graph_begin()이 리턴한 전용 스트림 핸들로 강제 가능

✅ 결과: ops 코드는 파이썬 dict 파싱/키 문자열/타입 처리에서 해방되고, 캡처/재현성도 좋아짐.


2) 레지스트리/디스패치 변화 (선택 규칙을 한 군데로)

새 규칙

  • KernelRegistry가 OpKind -> [KernelVariant]를 priority 내림차순으로 보관
  • Dispatch()가:
    1. 입력 검증(포인터/갯수/stream 필수)
    2. AttrBlob normalize (없으면 EmptyAttrBlob)
    3. variants snapshot 후 supported()로 매칭
    4. workspace 요구 있으면(>0) v0에서는 NotImplemented로 컷
    5. launch() 호출

✅ 결과: op별 launcher에서 “공통 예외처리/선택/정책”을 빼고, 커널 로직에 집중.


3) ops 코드 변화 규칙 (launcher.cu 패턴)

각 op는 src/backends/cuda/ops/<op>/launcher.cu에서 다음 고정 구조를 따른다.

(A) include 정책

  • ❌ aicf/core/*, aicf/runtime/*, _common/shim/* 전부 제거
  • ✅ 필요한 최소만:
    • cuda_runtime.h, cuda_fp16.h 등
    • aicf/backends/cuda/registry/{tensor_desc, kernel_variant, attr_blob, status}.hpp

(B) Attr 파싱

  • AttrBlob schema를 op마다 정의:
    • 예) ReduceSum: schema_id = 'RSUM', payload: int64 axis
    • 예) Gemm: schema_id = 'GEMM', payload: int32 ta, int32 tb
  • schema_id == 0일 땐 “기본값 허용” (MVP 친화)
  • payload 길이 부족하면 기본값/InvalidArgument 중 선택(일관되게)

(C) supported() / launch() 분리

  • supported()는 싸게: rank/shape/dtype/contig/stride + attr 값 정도만 확인
  • launch()는 엄격하게: supported와 동일 조건을 다시 확인하고 kernel launch
  • CUDA 에러처리는 shim 없이 직접:
    • cudaPeekAtLastError() 또는 cudaGetLastError()로 Status::Internal 반환

(D) 스트림 정책

  • launcher는 cudaStream_t stream을 인자로 받고 그걸 그대로 쓴다.
  • Dispatch()에서 call.stream == nullptr이면 InvalidArgument로 컷.
  • (캡처 일관성) 바인딩에서 graph_begin이 준 stream_handle로 op_call을 호출하면, 전 ops가 같은 스트림을 사용.

4) 테스트 규칙 (python_binding_test)

각 op마다 examples/python/python_binding_test/v2_<op>_probe.py 작성.

필수 항목:

  • enum 값 출력 (print(int(_C.OpKind.X)))
  • Positive 케이스: 최소 1개 shape
  • (가능하면) 조합 테스트: transpose/fastpath 등 분기 커버
  • Negative 케이스:
    • shape 불일치 or axis invalid
    • 기대 결과: NotImplemented 혹은 InvalidArgument를 의도대로 받는지 확인

수용 오차 가이드:

  • F32: atol ~ 1e-5
  • F16 출력(half 저장): atol ~ 1e-2까지 허용 가능(라운딩 step 1/128 고려)

5) “op 추가” 체크리스트 (짧게)

  1. include/aicf/backends/cuda/ops/<op>/api.hpp가 core-free인지 확인(불필요 include 제거)
  2. launcher.cu에서 AttrBlob schema 정의 + supported/launch + KernelVariant factory 작성
  3. register_all.cpp에 factory 등록 (우선은 필요한 op만)
  4. CMakeLists에 launcher 추가
  5. v2 probe 테스트 추가 + 통과 로그 남기기