이 문서는 현재 프레임워크(파이썬 Sequential ↔ C++/CUDA graph_executor)에서 Shape 해석, Loss 입력 규칙, 레이어 간 형상 연결 규칙을 일관되게 사용하는 방법을 정리합니다. 최근 수정으로 모든 손실과 레이어가 per‑sample 규칙으로 통일되었습니다.
1) 핵심 원칙 (Per‑Sample Shape Rule)
- Shape(rows, cols) 는 "배치 내 단일 샘플의 표현" 입니다.
- 배치 정보는 Shape에 포함하지 않습니다.
- C++ 엔트리에서 실제 전체 연산 크기는 다음처럼 계산합니다.
- rows_per_sample = sp.rows (예: 1, 또는 시퀀스 길이)
- C = sp.cols
- B = batch_size * rows_per_sample
- N = B * C
- Python → C++로 batch_size는 별도 인자로 전달합니다.
2) Shapes 맵 운용 규칙
- 모든 Op의 to_e_matrix(input_id)는 반드시 아래를 shape_map에 등록해야 합니다.
- 입력 텐서: shape_map[input_id] = Shape(rows_in, cols_in)
- 출력 텐서: shape_map[output_id] = Shape(rows_out, cols_out)
- 가중치/바이어스: 존재 시 shape_map[weight_id], shape_map[bias_id]
- 첫 Op(예: Flatten)에서도 입력 shape를 반드시 등록해야 합니다. (미등록 시 missing input shape 에러)
- 호환성(구 엔트리) 위해 파이썬에서 다음 별칭을 추가해도 좋습니다.
- self.shapes["shape"] = self.shapes[self.output_var]
3) 레이어별 Shape 표준
3.1 Flatten
- 입력: (B, C, H, W) 또는 (B, ..., D)
- 입력 per‑sample: Shape(1, C*H*W) (또는 1 × ∏dims)
- 출력 per‑sample: Shape(1, C*H*W)
- 주의: shape_map[input_id]와 shape_map[output_id]를 모두 등록
3.2 Dense (전결합)
- 입력 per‑sample: Shape(1, input_dim)
- 가중치: Shape(input_dim, units)
- 바이어스: Shape(1, units)
- 출력 per‑sample: Shape(1, units)
3.3 Conv2D (권장 기본 형태)
- 내부 컨볼루션 표현: (filters, H_out*W_out) 를 사용할 수 있으나,
- 공식 출력은 Flatten을 포함하여 per‑sample을 Shape(1, filters*H_out*W_out) 로 반환합니다.
- 이렇게 하면 Conv → Dense가 자연스럽게 연결됩니다.
- 옵션: Conv→Conv→Pool 체인이 많은 경우, emit_flatten=False 옵션을 두고 별도 Flatten 레이어로 처리할 수 있습니다. 단, 그 경우 후속 레이어가 (filters, H_out*W_out)를 처리하도록 설계되어야 합니다.
3.4 Pooling/Activation/Softmax 등
- 입력과 출력의 per‑sample rows/cols가 동일하거나, 사양에 따라 변환되더라도 반드시 input/output 양쪽 Shape를 등록합니다.
- Softmax(Classification head) 출력은 보통 Shape(1, num_classes).
4) 손실 함수 입력 규칙 (통일)
모든 손실은 per‑sample 규칙으로 라벨 Shape를 기대합니다. (배치는 별도)
- MSE
- y_pred per‑sample: Shape(r, C) (일반적으로 r=1)
- y_true per‑sample: Shape(r, C)
- 검증: st.rows * st.cols * batch_size == B * C
- BCE (binary_crossentropy)
- y_pred per‑sample: Shape(r, C) (시그모이드 확률)
- y_true per‑sample: Shape(r, C) (float32 0/1)
- 검증: MSE와 동일 방식
- CCE (categorical_crossentropy, 원‑핫)
- y_pred per‑sample: Shape(r, C=num_classes) (소프트맥스 확률)
- y_true per‑sample: Shape(r, C) (원‑핫 벡터, float32)
- 검증: st.rows == rows_per_sample && st.cols == C
- 전체 연산은 커널에서 (B, C) 범위를 사용 (B = batch_size * rows_per_sample)
주의: 현재 C++ 엔트리는 mse/bce/cce만 지원. sparse_cce를 쓰고 싶으면 별도 구현이 필요합니다.
5) 라벨/출력 텐서 dtype 규칙
- MSE/BCE/CCE(원‑핫): y_true → float32
- y_pred는 각 손실에 맞는 범위/활성화를 거친 float32 (예: BCE 시그모이드, CCE 소프트맥스)
- sparse 변형을 추가할 경우: y_true는 정수형(int32/64) 유지하도록 별도 경로 구현
6) 최종 출력 버퍼(y_pred) 관리
- train_step_entry()는 tensors[pred_id] 포인터를 이미 존재한다고 가정합니다.
- Python 쪽에서 tensor_ptrs[self.output_var] = cp.empty(B*C, dtype=cp.float32).data.ptr 처럼 사전 할당하여 꽂아 주세요.
- B*C = out_shape.rows * out_shape.cols * batch_size
- evaluate()도 동일하게 처리합니다. (predict()는 별도 out_host 경로 사용 가능)
7) Graph(E) & OpStruct 연결 규칙
- 각 노드: {op_type, input_id, param_id, output_id, extra_params}
- LOSS 노드는 E의 마지막에 배치, input_id = model.output_var, param_id = "y_true", output_id = "loss"
- C++에서 final_output_id == "loss"면 pred_id = E.back().input_id 로 해석합니다.
8) 자주 나는 실수 & 디버그 체크리스트
- 첫 Op의 입력 shape 미등록 → missing input shape: input
- 해결: 모든 to_e_matrix에서 shape_map[input_id]를 등록
- y_pred 포인터 미제공 → missing y_pred tensor: <output_id>
- 해결: Python에서 최종 출력 버퍼 사전 할당 후 tensor_ptrs[self.output_var]로 제공
- CCE에서 라벨 Shape 불일치 → pred(B=...,C=...) vs true(1,3)
- 해결: CCE도 per‑sample 규칙 사용 (y_true per‑sample을 (rows_per_sample, C)로)
- Dense/Conv 연결 시 형상 충돌
- 해결: Conv의 공식 출력은 (1, F*H*W) (Flatten 포함)로 통일하거나, emit_flatten=False+별도 Flatten 레이어 사용
- dtype 불일치
- 해결: 현재 손실 라벨은 float32(원‑핫/0‑1). sparse 경로는 정수 라벨을 허용하는 별도 구현 필요
9) 미니 예시
9.1 XOR (BCE)
- 입력: (B,1,1,2) → Flatten: Shape(1,2) → Dense(4) → Tanh → Dense(1) → Sigmoid
- 출력 per‑sample: Shape(1,1)
- 라벨 per‑sample: Shape(1,1) (0/1, float32)
- Python에서 y_pred 버퍼 사전할당 후 train_step_entry() 호출
9.2 3-Class Softmax × CCE
- 입력: (B,1,1,3) → Flatten: Shape(1,3) → Dense(32) → GELU → Dense(3) → Softmax
- 출력 per‑sample: Shape(1,3)
- 라벨 per‑sample: Shape(1,3) (one‑hot, float32)
- C++ CCE 경로는 per‑sample 규칙으로 검사, 전체 커널은 (B,C) 처리
10) 권장 코딩 패턴 요약
- to_e_matrix는 항상 input/output/params shape 등록
- Conv의 공식 출력은 (1, F*H*W)로 통일(기본), 필요한 경우 옵션화
- 손실은 mse/bce/cce 전부 per‑sample 규칙
- 파이썬 엔트리에서 최종 출력 버퍼 사전할당
- 필요시 self.shapes["shape"] 별칭으로 하위 호환 확보
- C++에서 결측 키/shape/size mismatch를 구체적으로 로그
이 가이드를 팀/문서에 포함하면, 레이어 추가/커널 확장 시 Shape 관련 버그를 초기에 차단할 수 있습니다. 다음에 새 레이어를 추가한다면, 위 10) 패턴을 템플릿으로 삼아 to_e_matrix를 작성하세요.
11) 텐서 메모리/레이아웃 규칙
- 기본 레이아웃: Python 입력은 관례상 NHWC로 전달하지만, per‑sample Shape는 항상 (rows, cols)로 표현합니다.
- 연속성: 모든 텐서는 C‑contiguous(np/cp.ascontiguousarray) 보장.
- 정렬/배치: float32 4‑byte 정렬 가정. 다른 dtype 사용 시 커널/shape 규칙을 별도 정의.
12) ID/키 네이밍 컨벤션
- input 고정 입력 키.
- 각 레이어는 name 기반으로 출력/중간 ID를 생성: <name>_conv, <name>_linear, <name>_out, <name>_act, <name>_preact 등.
- 학습 라벨 키는 y_true 고정. 최종 로스 노드 출력은 loss.
13) OpType & ExtraParams 규약
- OpType 예: FLATTEN=5, CONV2D=6, LOSS=7, RELU=2, SIGMOID=3, TANH=4, ADD=1, DENSE=0(Linear) 등(엔진 enum과 동기화).
- OpExtraParams 필드 의미:
- batch_size(힌트), input_{h,w,c}, output_c, kernel_{h,w}, stride_{h,w}, padding_{h,w}, use_bias 등.
- 연산마다 참조 여부가 다를 수 있으나 일관된 단위/의미 유지 (padding은 한쪽 값).
14) 파라미터 Shape 규약
- Dense: W=(in_dim, units), b=(1, units).
- Conv2D(직렬화 형태): W=(filters, in_c*kh*kw), b=(1, filters) 또는 타일(broadcast 미지원 시 (1, filters*H_out*W_out)).
- Shape 맵에는 파라미터도 반드시 등재.
15) 브로드캐스트 규약 (Bias 등)
- 기본 브로드캐스트는 행우선(채널) 기준.
- 엔진이 (1, F) → (B, F*H*W) 브로드캐스트를 지원하지 않으면 force_bias_tile=True로 (1, FHW) 사전 타일링.
16) 포인터 맵 & 버퍼 소유권
- Python이 제공: tensors(입력/파라미터/y_true/y_pred), 옵티마 상태(velocity/m/v 등).
- 최종 출력 버퍼(y_pred) 는 Python이 사전 할당 후 tensors[self.output_var]로 제공.
- 그래디언트 버퍼는 C++에서 cudaMalloc 후 optimizer_update_cuda 호출 뒤 엔진이 해제.
17) Gradients 맵 규칙
- 키: 파라미터 ID (weight_id, bias_id).
- Shape: 파라미터 shape와 동일.
- 누락 가능성: 학습 불가(op_type 상 비가중치)인 경우 경고만 로그.
18) 옵티마 상태 포인터 & Shape
- 각 파라미터에 대해 velocity/m/v(그리고 옵션 vhat)를 같은 shape로 제공.
- 제공되지 않으면 옵티마에서 해당 기능 비활성 또는 기본 0 버퍼로 처리(현재는 Python에서 모두 제공 권장).
19) 수치 안정성 규약
- softmax/log/exp 사용부는 epsilon clamp 적용 (eps=1e-8 권장).
- BCE/CCE는 입력 확률/로짓 일관화: BCE는 시그모이드 후 확률, CCE는 소프트맥스 확률 기준.
- NaN 감지 시: 소스별(입력/파라미터/중간) 덤프용 디버그 플래그 제공 권장.
20) 패딩/스트라이드/출력 크기 공식
- Conv2D (SAME): H_out = ceil(H_in / s_h), W_out = ceil(W_in / s_w).
- VALID: H_out = floor((H_in - k_h + 1) / s_h), W_out = floor((W_in - k_w + 1) / s_w).
- padding_{h,w}는 한쪽 값(대칭 가정). 총 패딩은 2*padding_*.
21) Pooling 규약
- Max/Average Pool: 입력/출력 per‑sample shape를 명시. 커널/스트라이드/패딩은 Conv와 동일 의미.
- 출력 feature 크기 산식은 Conv와 동일 공식을 사용.
22) Activation 매핑 표
- relu→OpType.RELU, sigmoid→SIGMOID, tanh→TANH, gelu→GELU(있다면), softmax→SOFTMAX.
- 각 활성화는 입력/출력 Shape 동일. 수치 안정화 주의(특히 softmax).
23) 테스트 규약
- 미니 스모크: evaluate()가 NaN 없이 실행, fit() 후 손실 감소.
- 회귀 테스트: XOR(BCE), 3‑Class(Softmax×CCE), 임의 MSE 회귀.
- 디버그: GE_DEBUG_SYNC/CUDA_LAUNCH_BLOCKING 옵션으로 첫 에러 조기 표면화.
24) 로깅/에러 메시지 규약
- 누락 항목을 구체적으로 표시: tensors[<id>], shapes[<id>].
- 사이즈 불일치 시 기대치/실측치를 모두 출력: pred per-sample=(r,C), y_true=(r',C'), batch=B.
- 경고와 예외의 레벨 구분(continue vs throw).
'dev_AI_framework' 카테고리의 다른 글
| bias — Row/Col-wise Bias Add Kernels (0) | 2025.08.23 |
|---|---|
| activation - CUDA 활성화 커널 모듈 (2) | 2025.08.23 |
| cnn_layer 구현 및 수정 (0) | 2025.08.22 |
| Regression 에서 오류 발생, XOR 은 잘 되는데 왜...? - 해결완료 (0) | 2025.08.19 |
| 후에 구현할 기능 고민해보자... (5) | 2025.08.15 |