CPU Reference와 검증 루프
CUDA kernel을 만들 때 첫 목표는 빠른 코드가 아니라 맞는 코드다.
CPU reference
GPU kernel
elementwise compare
max error / tolerance
Input
CPU reference
expected
CUDA kernel
actual
compare
max error <= tolerance
이 순서가 있어야 이후 최적화에서 결과가 깨졌는지 바로 알 수 있다.
CPU reference
CPU reference는 느려도 된다. 중요한 것은 읽기 쉽고 믿을 수 있어야 한다는 점이다.
void add_cpu(const float* a, const float* b, float* c, int n) {
for (int i = 0; i < n; i++) {
c[i] = a[i] + b[i];
}
}
새 CUDA kernel을 만들 때는 먼저 같은 연산을 CPU loop로 구현한다.
비교 방식
float 연산은 완전히 같은 bit가 나오지 않을 수 있다. 그래서 보통 tolerance를 둔다.
float diff = fabs(cpu_value - gpu_value);
if (diff > tolerance) {
// mismatch
}
처음에는 작은 shape로 직접 출력하고, 그다음 큰 shape에서 전체 mismatch를 확인한다.
작은 shape:
row/col/index를 눈으로 확인
큰 shape:
max absolute error와 mismatch count 확인
PyTorch reference
PyTorch extension으로 넘어가면 reference는 PyTorch로 만들 수 있다.
ref = torch_op(x)
out = custom_cuda_op(x)
torch.testing.assert_close(out, ref, rtol=1e-4, atol=1e-4)
확인
- CUDA kernel을 최적화하기 전에 CPU reference가 필요한 이유는 무엇인가?
- 작은 shape와 큰 shape 검증은 각각 무엇을 잡아내는가?
- float 비교에서 tolerance가 필요한 이유는 무엇인가?