본문 바로가기

dev_AI_framework

포인터/변수 참조 이슈 정리

1) 포인터 소유권/수명 모델 (ownership & lifetime)

  • 파이썬 제공 포인터
    • x, y, weights, bias 는 파이썬 쪽에서 할당된 디바이스 메모리 주소, (.data.ptr) 가 pybind 로 넘어옴
    • 파이썬에서 참조를 유지해야 CuPy CG 가 메모리를 회수하지 않음, self.tensor_map[name] = cp_arr 같은 보관이 필수
  • 프레임워크(런타임) 임시 버퍼
    • run_graph_cuda() 가 중간 output 을 cudaMalloc 하여 새로 만든 디바이스 버퍼
    • forward 종료 뒤 정리 대상
  • 정리 규칙
    • forward 시작 지점에 user_keys = set(tensor_ptrs.keys()) 스냅샷
    • forward 끝난 뒤 for (auto& kv : tensors) if (kv.first not in user_keys and kv.first != pred_id ) cudaFree(kv.second);
    • 위와 같이 하면 사용자 포인터/최종 출력만 남고, 나머지 임시 버퍼는 정리됨

 

2) ID와 버퍼의 불변식 (naming invariants)

  • 각 연산은 (input_id, param_id, output_id)를 가짐. 이 문자열 ID가 키가 되어 tensors[id] -> float* 로 매핑됨.
  • LOSS 노드의 입력이 바로 모델의 “예측” 텐서여야 함.
    • BCE의 y_pred는 마지막 Activation(Sigmoid) 출력.
    • 실수로 **Dense 선형 출력(로짓)**을 y_pred로 쓰면 음수/1초과가 들어가서 손실/기울기가 폭주하거나 0/16로 튐.
  • 따라서 손실 계산부에서:
    • if E.back().op_type == LOSS: pred_id = E.back().input_id else: pred_id = final_output_id
    • 그리고 반드시 y_pred = tensors[pred_id] 를 사용.

 

 

3) 배치 포인터 오프셋 (per-batch pointer arithmetic)

  • 내부 커널은 각 배치 샘플에 대해 input_b = base + b * (rows*cols) 형태로 오프셋을 더함.
  • **shape의 rows/cols는 “한 샘플 크기”**로 유지하고, 실제 할당은 batch_size * rows * cols 바이트.
  • forward/backward/손실 모두 동일한 규칙으로 오프셋을 계산해야 함.
    • 어느 한 곳에서라도 “배치 차원 없는 길이”로 계산하면 엉뚱한 위치를 읽거나 씀.

 

4) 흔한 참조 실수와 증상

BCE에 로짓을 y_pred로 전달 손실이 0 ↔ 16 근처로 튐, y_pred에서 음수/1초과 관찰 손실 계산 직전 주소 출력: [PTR] y_pred=…, act_out=…, dense_out=… → y_pred가 act_out과 동일해야 정상
임시 버퍼를 free하지 않음 evaluate()나 긴 학습 중 멈춤/다운 forward 종료 후 user_keys 외 버퍼 cudaFree
CuPy 배열이 GC로 해제됨 랜덤 크래시/멈춤/쓰레기 값 파이썬에 참조(딕셔너리)에 영구 보관
BCE 경로에서 중복 평균 (예: backward가 /batch 했는데 어딘가에서 /size 추가) 기울기 너무 작아서 학습 정체 bce_loss_backward 외에 추가 나눗셈 전역 검색(/ size, / n, / batch 등)
포인터 alias/in-place 입력/출력이 같은 버퍼로 alias되면 덮어써서 오동작 각 op에서 input/output이 서로 다른 포인터인지 점검(의도적 in-place 아니면 금지)