본문 바로가기

dev_AI_framework

📄 문서: e2e f16 GEMM + Bias + ReLU 테스트 동작 과정

그래프 실행기 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 는 다음 단계를 수행

  1. Pass 실행 (canonicalize, fuse_elementwise 등 ) -> 아직 미구현
  2. pick_kernel(op, device_caps) 로 최적 커널 선택 -> capability 테이블에 의해 gemm_bias_act_fc_f16 선택
  3. _prepare_buffers(op) 에서 IR Tensor 의 .t 에서 디바이스 포인터 추출 -> bufs 배열 구성
  4. 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 미만이면 성공