[CUDA 공부] 1-2. elementwise.py

NJ·2025년 8월 21일
0

CUDA공부

목록 보기
2/4

https://github.com/xlite-dev/LeetCUDA/blob/main/kernels/elementwise/elementwise.py
위의 python 코드에 대한 설명 (from GPT)

이 파이썬 스크립트는 CUDA 커널을 PyTorch 확장으로 빌드/로딩하고, 여러 텐서 크기(S, K)에서 커스텀 커널 vs PyTorch 연산의 성능을 반복 벤치마크하는 코드.


1) CUDA 확장 로드

from torch.utils.cpp_extension import load

lib = load(
    name="elementwise_lib",
    sources=["elementwise.cu"],
    extra_cuda_cflags=[
        "-O3",
        "-U__CUDA_NO_HALF_OPERATORS__",
        "-U__CUDA_NO_HALF_CONVERSIONS__",
        "-U__CUDA_NO_HALF2_OPERATORS__",
        "-U__CUDA_NO_BFLOAT16_CONVERSIONS__",
        "--expt-relaxed-constexpr",
        "--expt-extended-lambda",
        "--use_fast_math",
    ],
    extra_cflags=["-std=c++17"],
)
  • load(...)elementwise.cu컴파일 → .so 생성 → 파이썬 모듈로 로드까지 한 번에 해줘.

  • 첫 실행은 컴파일 때문에 시간이 길어질 수 있음(이후 캐시된 .so 재사용). (맞아. 꽤 오래 기다려야 했음. 이 코드를 돌리실 분들이라면 조급해하지 말고 기다려보길)

  • extra_cuda_cflags: (이 부분은 잘 모르지만 그냥 넘어갔다)

    • -O3, --use_fast_math: 최적화/빠른 수학 함수.
    • -U__CUDA_NO_HALF_*: half/half2/bfloat16 연산 intrinsics 활성화.
    • --expt-relaxed-constexpr, --expt-extended-lambda: CUDA C++ 확장 기능 허용.

로드되면 lib.elementwise_add_f32, lib.elementwise_add_f16x8_pack 같은 바인딩된 파이썬 함수로 커널을 호출할 수 있어.


2) 자동 미분 끄기

torch.set_grad_enabled(False)
  • 벤치마크용이므로 autograd 비활성화 → 오버헤드 감소.

3) 벤치마크 함수

def run_benchmark(perf_func, a, b, tag, out=None, warmup=10, iters=1000, ...):
  • 입력

    • perf_func: 실행할 함수(커스텀 커널 또는 torch.add).
    • a, b: 입력 텐서.
    • out: 출력 버퍼(있으면 in-place 스타일, 없으면 함수가 반환값 생성).
    • warmup: 워밍업 반복 횟수(캐시/클럭 워밍업).
    • iters: 실제 측정 반복 횟수(평균 시간 산출).
  • 동작

    1. out.fill_(0)로 출력 초기화(있을 때).
    2. 워밍업 루프 수행.
    3. torch.cuda.synchronize()로 GPU 작업 동기화 후 타이머 시작.
    4. iters만큼 호출(동기화 후 종료 시각 측정).
    5. 평균 실행 시간(ms) 계산.
    6. 결과 텐서의 앞 2개 값만 CPU로 가져와 출력(정확도 확인).
  • 중요 포인트

    • CUDA는 비동기라서, 정확한 시간 측정을 위해 앞뒤로 torch.cuda.synchronize()가 필수.
    • iters=1000이라 꽤 오래 걸릴 수 있음(정밀 측정 목적).

4) 실험 (텐서 생성 & 반복)

Ss = [1024, 2048, 4096]
Ks = [1024, 2048, 4096]
for S, K in SKs:
    a = torch.randn((S, K)).cuda().float().contiguous()
    b = torch.randn((S, K)).cuda().float().contiguous()
    c = torch.zeros_like(a).cuda().float().contiguous()
  • 9개 크기 조합(S,K) 각각에 대해 텐서 생성(GPU, FP32, contiguous).

  • FP32 벤치마크

    • lib.elementwise_add_f32(a,b,c) : 스칼라형 FP32 커널
    • lib.elementwise_add_f32x4(a,b,c) : float4 벡터화 버전
    • partial(torch.add, out=c) : PyTorch 기본 연산(비교군)
  • FP16 벤치마크

    a_f16 = a.half().contiguous(); b_f16 = b.half().contiguous(); c_f16 = c.half().contiguous()
    • lib.elementwise_add_f16 : half 스칼라
    • lib.elementwise_add_f16x2 : half2 벡터화
    • lib.elementwise_add_f16x8 : half2×4로 8개 처리
    • lib.elementwise_add_f16x8_pack : 128비트 일괄 로드/스토어 + half2 연산(가장 공격적 최적화)
    • partial(torch.add, out=c_f16) : PyTorch FP16 비교군

각 호출은 run_benchmark(...)으로 감싸서 평균 시간과 샘플 출력값을 찍어 줘.


5) 출력 포맷

-------------------------------------
                                        S=1024, K=1024
           out_f32: [v1, v2], time:0.00xxms
         out_f32x4: [v1, v2], time:0.00xxms
        out_f32_th: [v1, v2], time:0.00xxms
-------------------------------------
           out_f16: [...]
         out_f16x2: [...]
         out_f16x8: [...]
     out_f16x8pack: [...]
        out_f16_th: [...]
-------------------------------------
  • out_*: 결과 텐서 앞 2개의 값(정확도 체크)
  • time:: 1회 평균 소요 시간(ms)

6) 왜 느릴 수 있나 (정상임)

  • 처음 load(...) 컴파일 비용.
  • 각 조합마다 워밍업 10회 + 측정 1000회 × 커널 8개 × 사이즈 9개 → 호출 수가 많음.
  • 정확한 측정을 위해 synchronize()가 들어가 있어 CPU가 GPU 끝날 때까지 대기.

빠르게 돌리고 싶다면: iters 줄이기(예: 100), S/K 조합 축소, show_all=False 유지.


7) 안전/품질 포인트

  • .contiguous()로 메모리 연속 보장(벡터화/128비트 접근에 중요).
  • FP16 커널은 내부에서 __hadd/__hadd2 사용(half/half2 연산).

GPT가 설명을 알잘딱깔센으로 잘해줘서 궁금한 부분만 조금 더 물어보고 python 코드는 더 파지 않았다.
왜냐... 진도 나가야 해.
쿠다 코드 보는 걸 우선으로 하고 있는데, 쿠다 library를 실행할 파이썬 코드도 궁금하면 다시 돌아오는 걸로..!

profile
Studying NLP

0개의 댓글