1) 성능·수학 모드
- CuPy: 기본적으로 cuBLAS/ cuSOLVER를 호출합니다. Ampere+에서는 TF32가 기본 허용된 환경이면 matmul에서 텐서코어를 씁니다. 필요하면 cupy.cuda.cublas로 math mode / allow_tf32를 바꿀 수 있고, dtype을 float16/bfloat16/float32로 바꿔도 상층이 알아서 cuBLASEx 경로를 밟아요. 즉, “웬만하면 이미 최적 경로”.
- 직접 래퍼: 네 코드는 CUBLAS_COMPUTE_32F_FAST_TF32를 명시하고 있어 TF32 사용이 확실합니다. 또한 StridedBatchedGemmEx를 직접 태우므로, CuPy의 브로드캐스팅+배치보다 정확히 원하는 배치 레이아웃/stride로 돌려 오버헤드를 줄일 수 있어요. 특정 문제에서 수%~수십% 이득을 볼 수 있습니다.
- 반대로, 단건 GEMM만 드물게 돌거나 크기가 애매하면 차이는 미미할 수도.
2) 레이아웃/전치 비용
- CuPy: A @ B는 필요시 내부 전치/브로드캐스팅/contiguous 보장을 위해 임시 버퍼를 만들 수 있음. 대부분의 케이스에서 크게 문제는 안 되지만, 초미세 튜닝엔 눈에 밟힐 때가 있어요.
- 직접 래퍼: 네가 쓴 row-major 매핑(매개변수 스왑: opB, opA, N,M,K 순서, ldb, lda)은 정석입니다. 이 방식으로 불필요한 실제 전치 없이 row-major 텐서를 column-major 기대의 cuBLAS에 정확히 맞출 수 있어 캐시/메모리 이동을 최소화할 수 있어요.
3) 배치 처리 방식
- CuPy: 3D 이상 텐서의 matmul은 브로드캐스팅 규칙으로 배치 연산을 수행. 내부에서 strided-batched로 최적화되기도 하지만, 입력이 비연속/브로드캐스트면 중간 materialize가 생길 수 있음.
- 직접 래퍼: cublasGemmStridedBatchedEx에 stride를 직접 넣기 때문에, 메모리 배치를 네가 설계한 그 상태로 바로 태움. CNN 1×1 conv처럼 작은 GEMM을 아주 많이 돌릴 때 명확한 이점.
4) 스트림/핸들/동기화 제어
- CuPy: 스트림은 cupy.cuda.get_current_stream()에 매칭된 cublas handle 캐싱을 해줍니다. 대부분 신경 안 써도 좋아요.
- 직접 래퍼: 핸들/스트림을 네가 명시 관리하므로 커스텀 커널과 정밀한 파이프라이닝(동일 스트림 이어붙이기, 이벤트 동기화, CUDA Graph 캡처) 가능. 반면 실수하면 숨은 동기화나 핸들 라이프사이클 문제로 성능이 뚝 떨어질 수 있음.
5) 수치/결정론
- CuPy: 기본 cuBLAS 결정론 정책을 따릅니다(결정론 강제 옵션 제공). 고정 소수점/감산 순서/TF32 허용 여부는 환경과 설정에 좌우.
- 직접 래퍼: CUBLAS_COMPUTE_32F_FAST_TF32로 명확한 수치 정책. 필요하면 CUBLAS_WORKSPACE_CONFIG, CUBLAS_MATH_DISALLOW_REDUCED_PRECISION_REDUCTION 등으로 결정론/정밀도를 직접 강제 가능.
6) 기능 범위 & 유지보수
- CuPy: GEMM/GEMV 말고도 브로드캐스트, 고수준 ufunc, 고급 감축, indexing, FFT/Sparse까지 폭넓게 지원. 버그/호환성 부담을 커뮤니티가 떠안음.
- 직접 래퍼: 네가 쓴 두 함수는 GEMM 한정. 필요 기능이 늘수록 래퍼/검증 코드가 늘어나고, 빌드·배포(윈도우 .pyd, 리눅스 .so), CUDA 버전 호환 부담이 상승.
7) Python↔CUDA 경계 비용
- CuPy: 이미 Python 레벨에서의 호출 오버헤드 최소화에 신경 씀. 메모리 풀(allocator)도 잘 돼 있어 임시 배열 비용이 낮음.
- 직접 래퍼: pybind11로 연결할 때 컨버전, 포인터 추출, 스트림 전달을 잘 하면 동일하거나 더 낮은 오버헤드가 가능하지만, 설계 실수 시 오히려 커질 수 있음.
8) 너의 CUDABackend 구현 관점 피드백
- dot: float(cp.dot(x, y).get())는 즉시 동기화를 유발(성능 하향 가능). 인터페이스가 host float를 요구한다면 어쩔 수 없지만, 가능하면 GPU 텐서 반환·상위에서 필요 시 host로 내리는 쪽이 더 비동기 파이프라인에 좋아요.
- _asdevice: object dtype 예외 처리 훌륭. 다만 A @ B 전에 **dtype 통일(float32/16/bfloat16)**을 보장하면 cuBLAS 경로 일관성이 좋아집니다.
- softmax/sigmoid의 clip은 안정성에 유리. 배치 큰 경우엔 in-place 연산(가능한 곳)이나 temp 최소화를 검토해볼 만.
- cholesky_solve: 실패 시 solve(A, Aty) 폴백도 OK. L2 정규화는 현재 방식으로 충분.
- 스트림: 필요하다면 cupy.cuda.get_current_stream().ptr로 현재 스트림을 잡아두고, 커스텀 커널/래퍼와 동일 스트림에서 실행되게 맞추면 교차 동기화 최소화.
9) 결론—언제 어떤 방식을?
- CuPy만으로 충분한 경우
- 모델/레이어 일반 연산, 큰/표준적 GEMM, 개발 속도 우선, 유지보수 리소스 제한.
- “그냥 빠르다” 수준이면 굳이 C++로 내려가지 않아도 됨.
- 직접 래퍼가 빛나는 경우
- 아주 많은 소형 GEMM(strided-batched, 정교한 stride 설계)
- 정확한 수학 모드/결정론/스트림 제어가 필요
- 커스텀 커널과 끼워맞추기(프롤로그/에필로그 fused op)로 메모리 왕복을 없앨 때
- CUDA Graph로 프레임 고정/지연 최소화를 노릴 때
'dev_AI_framework' 카테고리의 다른 글
| Attention/Transformer Layer 를 구현하려면 - 기저 연산들의 구현 필요 (1) | 2025.08.31 |
|---|---|
| attention, transformer layer 구현하기 - Attention, transformer 란? (1) | 2025.08.31 |
| BACKEND 부분 수정하기... 연산 방식의 수정 (0) | 2025.08.28 |
| GPU 백엔드와 GEMM/GEMV 에 대해... (1) | 2025.08.28 |
| GPU 구현, CPU 구현 비교 (0) | 2025.08.28 |