그래프 실행기 Executor 를 통해
- CuPy 로 만든 실제 GPU 텐서를 IR(Graph/Op/Tensor) 구조에 매핑하고
- 네이티브 커널을 호출하여 FP16 Tensor Core GEMM + FP32 Bias + ReLU 를 수행
- Numpy 참값과 결과를 비교하여 오차를 검증하는 과정
1. 문제 크기 및 입력 데이터 준비
M, N, K = 64, 96, 80
A_dev = (cp.random.randn(M, K) * 0.25).astype(cp.float16)
B_dev = (cp.random.randn(K, N) * 0.25).astype(cp.float16)
bias_dev= (cp.random.randn(N) * 0.10).astype(cp.float32) # FP32 bias
C_dev = cp.empty((M, N), dtype=cp.float16)
- M, N, K 는 행렬 곱의 크기
- A : M @ K
- B : K @ N
- C : M @ N
- A, B_dev : 랜덤 초기화된 FP16 행렬
- bias_dev : 길이 N 의 FP32 벡터, 출력 C 의 각 열에 더해짐
- C_dev : 결과를 담을 F16 버퍼 (비어 있는 상태)
여기까지는 CuPy 배열로 존재
2. IR 텐서 생성 및 실데이터 포인터 연결
A = Tensor(name="A", shape=(M, K), dtype="f16", layout="rowmajor", device="cuda"); A.t = A_dev
B = Tensor(name="B", shape=(K, N), dtype="f16", layout="rowmajor", device="cuda"); B.t = B_dev
Bias = Tensor(name="Bias", shape=(N,), dtype="f32", layout="rowmajor", device="cuda"); Bias.t = bias_dev
C = Tensor(name="C", shape=(M, N), dtype="f16", layout="rowmajor", device="cuda"); C.t = C_dev
- Tensor 클래스는 프레임워크 내부 IR(Intermediate Representation) 의 기본 단위.
- .t 필드에 CuPy 배열을 붙여줌 -> Executor 는 여기서 디바이스 포인터를 추출
- 각 텐서가IR 차원/타입 정보를 갖고 있으므로, 이후 Pass/Selector/Executor 에서 메타 데이터로 활용 가능
3. Op 구성 (fused GEMM + Bias + ReLU)
op = Op(
op_type="GEMM_BIAS_ACT",
inputs=[A, B, Bias],
outputs=[C],
attrs={"mnk": (M, N, K), "act": "relu"},
)
- op_type="GEMM_BIAS_ACT"
- 이미 MATMUL + BiasAdd + ReLU 가 하나의 연산으로 Fuse 되었다고 가정.
- inputs : A, B, Bias
- outputs : C
- attrs : 부가 속성 (행렬 크기, 활성화 종류)
Selector 는 이 정보를 기반으로 가장 적합한 커널을 선택한다.
4. Graph 구성
g = Graph()
g.ops = [op]
- 그래프 객체 생성 후, 단일 Op를 등록
- 실제 프레임워크에서는 Pass 를 돌며 여러 Op 가 fuse/최적화될 수 있다.
5. Executor 실행
ex = ExecutorV2(dry_run=False)
ex.run(g)
Executor 는 다음 단계를 수행
- Pass 실행 (canonicalize, fuse_elementwise 등 ) -> 아직 미구현
- pick_kernel(op, device_caps) 로 최적 커널 선택 -> capability 테이블에 의해 gemm_bias_act_fc_f16 선택
- _prepare_buffers(op) 에서 IR Tensor 의 .t 에서 디바이스 포인터 추출 -> bufs 배열 구성
- graph_executor_v2.launch_kernel(...) 호출 -> 네이티브 C++/CUDA 코드 실행
6. 참값 계산 (Numpy, CPU)
ref = (cp.asnumpy(A_dev).astype(np.float32) @ cp.asnumpy(B_dev).astype(np.float32))
ref = ref + cp.asnumpy(bias_dev).astype(np.float32)
ref = np.maximum(ref, 0.0) # ReLU
- CuPy 배열을 Numpy 로 옮기고 FP32 로 올림.
- @ 연산으로 GEMM 수행
- bias 벡터 더하기
- ReLU 적용
- 이론적 참값 획득
7. 결과 검증
mx_err = float(np.max(np.abs(ref.astype(np.float16).astype(np.float32)
- cp.asnumpy(C_dev).astype(np.float32))))
print("e2e f16 GEMM+Bias+ReLU max_err:", mx_err)
assert mx_err < 1.0
print("OK: ExecutorV2 e2e (f16 Lt) passed")
- ref 의 정밀도 다운 캐스팅
- C_dev 를 CPU 로 가져와 비교
- max_err 가 1.0 미만이면 성공
'dev_AI_framework' 카테고리의 다른 글
| cuBLASLt / CUTLASS 의 에필로그 epilogue 까지 흡수한다. - GEMM 연산 이후의 연산들 (bias add, activation, scaling 등 ) 의 커널 통합 ( 호스트 디스패치 + 커널 특화 방식, 나는 CUTLASS 가 맞을 듯??) (0) | 2025.09.03 |
|---|---|
| AI Compiler, Graph Executor 로드 (0) | 2025.09.03 |
| 📄 신규 기능 추가 과정 문서: FP16 TensorCore GEMM (cuBLASLt) (0) | 2025.09.02 |
| graph_executor_v2 변경 가이드 (무엇을 바꿀 때 어디를 고치는가) (0) | 2025.09.02 |
| graph_executor_v2 통합 문서 (수정 사항 + 빌드 & 스모크) (0) | 2025.09.02 |