본문 바로가기

dev_AI_framework

Dropout (CUDA) — 구현 문서

1) 개요

  • 입력 텐서 X:[M,N] (f32, RowMajor, CUDA)에 대해 학습 시 무작위로 일부 요소를 0으로 드랍하고, 필요 시 학습 시 스케일을 적용합니다.
  • 선택적으로 마스크 텐서를 생성·반환하여 역전파에서 재사용합니다.
  • 역전파는 dY와 mask를 받아 dX를 계산합니다.

 

2) 인터페이스 (C++)

2.1 Attributes

struct DropoutAttrs {
  float     p{0.5f};           // 드랍 확률 ∈ [0,1)
  bool      scale_in_train{true}; // 학습 시 1/(1-p) 스케일 적용 여부
  uint64_t  seed{0};           // RNG 시드 (0: 구현이 내부 시드/카운터 사용 가능)
};

 

  • scale_in_train = true:
    • Forward: Y = (mask ⊙ X) * (1/(1-p))
    • Backward: dX = (mask ⊙ dY) * (1/(1-p))
  • scale_in_train = false:
    • Forward: Y = mask ⊙ X
    • Backward: dX = mask ⊙ dY

 

2.2 런타임 API (backend CUDA)

Status DropoutCudaLaunch(
    const Tensor& X,
    Tensor& Y,
    Tensor* mask,                 // optional (nullptr 허용), I32 [M,N]
    const DropoutAttrs& attrs,
    StreamHandle stream);

Status DropoutCudaBackwardLaunch(
    const Tensor& dY,
    const Tensor& mask,           // I32 [M,N]
    Tensor& dX,
    const DropoutAttrs& attrs,
    StreamHandle stream);

 

Tensor 요구사항

  • 공통: device == CUDA, layout == RowMajor, shape는 2D.
  • X, Y, dY, dX: DType::F32, shape 일치.
  • mask: DType::I32, shape가 입력과 동일. 생성/사용 모두 2D int32.

2.3 얇은 엔트리 (ops)

int dropout_run(const Tensor& X, Tensor& Y, Tensor* mask,
                const ai::DropoutAttrs& attrs, StreamHandle stream);

int dropout_backward_run(const Tensor& dY, const Tensor& mask, Tensor& dX,
                         const ai::DropoutAttrs& attrs, StreamHandle stream);
  • 입력 검증 후 DropoutCudaLaunch / DropoutCudaBackwardLaunch 위임.
  • 성공 시 0, 실패 시 음수 에러 코드(-2 형식/장치/레이아웃/차원 문제, -3 shape mismatch, -7 런타임 오류 등).

3) 커널 구조

3.1 커널 런처 (kernels.cu)

void dropout_forward_kernel_launcher(const float* X, float* Y, int32_t* mask,
                                     int M_rows, int N_cols,
                                     float p, bool scale_in_train,
                                     uint64_t seed, cudaStream_t s);

void dropout_backward_kernel_launcher(const float* dY, const int32_t* mask, float* dX,
                                      int M_rows, int N_cols,
                                      float p, bool scale_in_train,
                                      cudaStream_t s);
  • 이름 충돌 방지를 위해 포인터 마스크는 mask, 행/열은 M_rows, N_cols로 통일.
  • 내부 스케일:
    scale = scale_in_train ? ((p < 1.f) ? 1.f/(1.f-p) : 0.f) : 1.f

3.2 Forward 커널 요약

  • 그리드: grid(M_rows), 블록: block(256)
  • 각 행(row)을 블록이 담당하고, 열 방향으로 stride loop.
  • 난수 생성: (seed ^ (row * LARGE_PRIME + col)) 형태 등으로 간단히 독립 난수 생성 (구현에 따라 Philox/XORWOW 대체 가능)
  • mask[i] = (rand >= p) ? 1 : 0
  • Y[i] = X[i] * mask[i] * scale
  • mask 포인터가 nullptr인 경우, 내부적으로 드랍을 수행하지만 마스크 기록을 생략(옵션). (권장: 역전파가 필요하면 반드시 마스크를 제공)

3.3 Backward 커널 요약

  • 입력: dY, mask
  • dX[i] = dY[i] * mask[i] * scale
  • scale는 Forward와 동일 규칙(학습 시 스케일 on/off)에 맞춤.

4) PyBind 바인딩 (요약)

  • Python에서 호출될 때는 내부적으로 TensorDesc를 생성하여 CUDA 메모리를 wrap하고, 위 얇은 엔트리를 통해 호출.
  • 테스트 용도 API (예):
    • ge.dropout(X, p=0.5, seed=123, return_mask=True) → (Y, mask)
    • ge.dropout_backward(dY, mask, p=0.5) → dX

(바인딩 함수명/시그니처는 프로젝트 내 py_api.cpp 정의에 따릅니다.)


5) 에러 처리/검증

  • 형식/장치/레이아웃 체크:
    • F32(입출력), I32(mask), 2D RowMajor, CUDA device
  • Shape 일치:
    • X, Y 동일 shape
    • mask 존재 시 X와 동일 shape
    • 역전파: dY, dX, mask shape 동일
  • CUDA 런치 오류:
    • cudaPeekAtLastError()로 확인하여 Status::RuntimeError로 전파
  • 에지 케이스:
    • p == 0 → mask ≡ 1, Y == X (scale=1 또는 1/(1-0)=1)
    • p → 1 → 모두 드랍(수치적으로 1은 금지). scale_in_train && p==1일 때 scale=0으로 정의해 overflow 회피.

6) 결정성(Determinism) & 시드

  • seed를 지정하면 동일 입력/시드/스트림에서 반복 실행 시 동일 마스크를 생성(커널 내 per-element 시드 조합 방식).
  • 멀티-스트림/멀티-런치 환경에서 완전 결정성을 보장하려면 고정된 seed + 일관된 launch partitioning을 유지해야 합니다.
  • 더 강한 결정성이 필요하면 Philox 기반 카운터 RNG로 전환 권장(현재는 간단한 per-element 혼합 시드).

7) 성능 메모

  • 256 thread/block 설정 (행당 1블록). 일반적 [M,N]에서 메모리 대역/분기 예측친화적.
  • 마스크는 int32로 저장(원자 연산 없음). 필요 시 uint8/bit-pack 최적화 가능.
  • 스케일은 커널 외부에서 상수 계산 후 인자로 전달 → 불필요한 분기 제거.

8) 사용 예시 (NumPy 기준)

import numpy as np
from graph_executor_v2 import _core as ge

M, N = 8, 16
X = np.random.randn(M, N).astype(np.float32)

# Forward (마스크 반환)
Y, mask = ge.dropout(X, p=0.3, seed=42, return_mask=True)

# Backward
dY = np.random.randn(M, N).astype(np.float32)
dX = ge.dropout_backward(dY, mask, p=0.3)
  • scale_in_train=True가 기본이므로 학습 시 통상적인 기대값 보존(E[Y]=X)이 됩니다.

9) 테스트 요약

  • 정상성: p=0 → Y==X, dX==dY
  • 스케일: scale_in_train=True일 때 드랍 후 평균 스케일링 검증
  • 결정성: 동일 시드 반복 호출 시 마스크 동일
  • 역전파: dX = mask ⊙ dY * scale 검증

10) 소스 트리 포인터

  • backends/cuda/ops/dropout/kernels.cu
    • dropout_fwd_kernel, dropout_bwd_kernel + 런처
  • backends/cuda/ops/dropout/launcher.cu
    • DropoutCudaLaunch, DropoutCudaBackwardLaunch
  • src/ops/dropout.cpp
    • ai::ops::dropout_run, ai::ops::dropout_backward_run
  • src/bindings/py_api.cpp
    • Python 바인딩 함수 (테스트 진입점)