《GPU Mode》
L056
2024
High priority
transcript · failed
Kernel Benchmarking Tales
Georgii Evtushenko (NVIDIA · CUB / Thrust 메인테이너) 의 GPU kernel 벤치마크 함정 모음. 워밍업 빠짐, cache 잔재, 컴파일러의 dead-code elimination, clock 의 jitter, 다른 process 의 메모리 압박 — 같은 코드가 5배씩 다르게 측정되는 자리들과, 그 자리를 잡는 방법론적 노트. 본 페이지는 transcript 가 실패해 NVIDIA cuda-samples, NVBench library, 그리고 Sebastian Aaltonen / Ryg / Andreas Köpf 의 공개된 benchmarking 가이드로 재구성됐다.
benchmark methodology
warmup
cache state
noise / jitter
CUDA Events
NVBench
dead-code elimination
publication bar
G
Speaker
Georgii Evtushenko
NVIDIA · CUB / Thrust 메인테이너 · NVBench 저자
§ 01강의가 풀려는 문제· why this lecture exists
“같은 코드, 같은 GPU, 5배 다른 측정값” 이 일상적으로 일어나는 자리
benchmark 는 의외로 어렵다. 코드를 짜고 시간만 재면 끝날 것 같지만 — 실제로는 같은 코드가 셋팅에 따라 30%, 50%, 200% 다르게 나온다. 그래서 “이 새 커널이 빠르다” 의 주장이 거의 모두 — 측정의 함정에 빠진 채로 발표된다.
강의의 두 출발 질문.
- 왜 같은 코드가 다르게 측정되는가 — warmup, cache, clock, jitter, 컴파일러 — 어느 것이 결정적 변수인가?
- 어떻게 정직한 측정을 짤 것인가 — methodology 의 표준이 무엇이고, NVBench 같은 도구가 자동으로 잡아주는 것이 무엇인가?
본 노트는 transcript 실패로 — Georgii 의 공개 talk (CppCon, GTC), NVBench 의 README 와 benchmarking philosophy 문서, 그리고 보다 일반적인 GPU benchmarking 의 community 지식 (Sebastian Aaltonen 의 blog, Andreas Köpf 의 PMPP 강의) 으로 재구성.
강의의 frame
“정직한 측정” 이라는 표현이 강의의 톤을 잡는다. 새 커널의 “speedup 2.5×” 가 정말 2.5× 인지, 아니면 baseline 의 측정이 잘못된 건지 — 그걸 분리하는 것이 benchmark 의 진짜 일.
“잘못된 측정으로 빠른 커널을 발표하는 것은 — 사용자에게 거짓을 파는 것이다. 우리가 깐 도구가 정말 빠른지 우리가 검증해야 한다.” 학습 노트 · 재구성
§ 02흔한 잘못된 측정· tales of woe
실전에서 자주 보는 함정 모음
#1
time.time() / clock_gettime호스트 시간으로 GPU 코드 측정
CUDA 는 비동기. kernel<<<...>>> 호출 직후 time.time() 은 launch overhead 만 잡힘. 실제 커널은 그 후 GPU 에서 도는 중. 첫번째 강의에서도 같은 함정 — L001.
#2
first call 만 측정워밍업 안 함
첫 호출에는 — CUDA context init, JIT 컴파일, kernel cache 미스가 모두 섞임. 두번째부터의 호출과 시간이 10~100배 차이. 실제 production 시간은 보통 “warm steady state”.
#3
cache 잔재같은 입력 반복
같은 input tensor 를 100번 반복 측정하면 — 모든 input 이 L2 cache 에 살아있어서 HBM 안 읽음. 실전 학습/추론에서는 이런 luck 못 받음. 결과가 memory-bound 인데 compute-bound 처럼 측정.
#4
dead-code elimination출력을 안 쓰면 컴파일러가 통째로 제거
kernel(out, in) 의 결과를 어디에도 안 쓰면 — 컴파일러가 “이 호출은 효과 없음” 으로 판단해서 커널을 통째로 제거할 수 있음. 시간 0 으로 측정. 이게 일어나는지 검증 필요.
#5
clock의 jitterGPU clock boost 가 비결정적
GPU 의 clock 이 thermal / power 에 따라 자동으로 boost up/down. 같은 코드가 시간에 따라 10% 차이. nvidia-smi 로 lock 하지 않으면 측정 결과의 표준편차가 큼.
#6
다른 process 의 영향shared GPU
cloud / shared cluster 에서 — 다른 사용자의 워크로드가 같은 GPU 의 메모리/PCIe/L2 를 공유. 측정값이 30% 까지 흔들림.
#7
launch overhead 와 kernel 시간 혼동작은 커널
kernel 자체가 1μs 인데 launch overhead 가 5μs. 측정값 6μs 가 “kernel 시간” 으로 해석되는 함정. 작은 커널의 비교는 항상 함정.
#8
async copy 의 시간 미포함memcpy 안 보임
stream 위 async memcpy 가 kernel 과 overlap 되는데 — kernel 만 측정하면 memcpy 시간이 빠짐. 실제 throughput 이 낮음.
함정의 패턴
위 8개 항목의 공통점 — “코드가 도는 환경의 어떤 변수를 안 통제했다”. cache 상태, clock 상태, compiler 의 결정, 다른 process — 모두 “외부 상태” 가 측정에 끼어드는 자리. 정직한 benchmark 는 이 외부 상태를 명시적으로 통제하거나 무력화한다.
§ 03워밍업의 중요성· cold vs warm
첫 호출은 항상 버린다 — 그리고 “충분히” 워밍업한다
cold call 은 — context init, kernel JIT, cache 첫 fill 이 섞여 매우 느림. warm call 만이 production 시간을 반영한다. 그래서 처음 N 회는 측정 전 “워밍업”으로 버린다.
FIG · cold → warm → hot 의 시간 변화schematic
call 1 (cold)
context init + JIT + first cache fill
~250 ms
cold call 이 250 ms — warm 의 25배. 이걸 평균에 포함시키면 측정값 완전히 깨짐. 경험칙 — 5~10 회 워밍업, 그 다음 100~1000 회 측정의 평균.
워밍업의 실전 패턴.
- 실제 커널을 N 번 실행 (보통 N=5~10).
cudaDeviceSynchronize() 로 모두 끝났는지 확인.
- 측정 시작 — CUDA Event 기반.
- 측정도 N 회 (보통 100~1000) 반복하고 통계 처리.
그러나 — workload 가 cold start 가 중요한 경우 (e.g., LLM serving 의 첫 query latency) 는 warm 만 측정하면 안 됨. cold/warm 두 시나리오를 분리해서 측정.
# 표준 워밍업 패턴 (PyTorch)
import torch
# 1. warmup
for _ in range(10):
out = kernel(in_data)
torch.cuda.synchronize()
# 2. measure
start = torch.cuda.Event(enable_timing=True)
end = torch.cuda.Event(enable_timing=True)
start.record()
for _ in range(1000):
out = kernel(in_data)
end.record()
torch.cuda.synchronize()
per_call_ms = start.elapsed_time(end) / 1000
cold start 측정의 별도 패턴
“처음 호출의 latency” 가 중요한 경우 — LLM 의 initial query, image inference 의 first request — cold call 만 따로 N 번 측정. 매번 fresh process 시작. Python script 의 시작 → 끝 시간 측정. 여러 번 반복해야 의미 있는 통계.
§ 04cache 상태가 결과를 뒤집는 자리· L1/L2/HBM 의 잔재
같은 함수 100번 호출 = 모두 cache hit
benchmark 의 가장 미묘한 함정. 같은 입력 데이터를 반복 측정하면 — 두번째 호출부터 데이터가 L2 cache 에 있음. HBM 을 안 읽음. memory-bound 워크로드가 compute-bound 처럼 측정. 실전에서는 매 호출이 fresh data 라 — measurement 와 production 의 throughput 이 다름.
두 가지 회피 패턴
(1) cache flush between calls — 매 call 사이에 cache 를 비움. NVBench 가 자동으로. 또는 충분히 큰 buffer 를 한 번 읽어 L2 evict. (2) different input each call — N 개의 다른 input 을 round-robin. cache 가 자연스럽게 evict.
cache 상태가 결과에 끼치는 영향을 정량화하면 —
- compute-bound 워크로드 (e.g., 큰 matmul) — cache 영향 거의 없음. 측정값 robust.
- memory-bound 워크로드 (e.g., elementwise, reduction, attention) — cache 영향 크고, 같은 입력 반복하면 30~100% 까지 빨라 보임.
- L2 cache 사이즈 — A100 40MB, H100 50MB. 입력이 그 안에 들어가면 “모든 측정 후가 cache hit”. 큰 입력은 자동으로 evict.
# NVBench 의 cache flush 패턴 (의사코드)
state.exec(
[&](nvbench::launch& launch) {
// flush L2 by reading large dummy buffer
flush_l2(launch.get_stream());
kernel<<<..., launch.get_stream()>>>(...);
});
// NVBench 가 자동으로 매 call 마다 flush
또 다른 미묘한 자리 — SM (Streaming Multiprocessor) 의 internal state. 같은 kernel 을 반복 호출하면 — instruction cache 도 hit. 이건 production 에서도 hit 하는 게 정상이라 보존. 그러나 다른 kernel 들이 섞여 도는 시나리오에서는 instruction cache miss 가 일어남. micro-benchmark 가 over-optimistic.
“cache 가 진실을 가린다 — 실전 시나리오의 cache 상태와 측정 시나리오의 cache 상태를 일치시키는 게 benchmark 의 본질.” 학습 노트 · 재구성
§ 05측정 도구별 차이· Event · nsys · NCU · NVBench
도구마다 “재는 것” 이 미세하게 다르다
도구 선택 가이드
(1) 한 줄 측정 → torch.utils.benchmark 또는 cuda Event. (2) 한 kernel 의 deep-dive → ncu. (3) 전체 학습 step → nsys. (4) publication 수준 비교 → NVBench. 도구를 섞어 쓰는 게 표준 — 한 도구 결과만으로 결론짓지 않는다.
각 도구의 미묘한 점들.
- CUDA Events — 가장 가볍지만 — kernel 외 시간 (host 처리, memcpy) 안 잡힘. 작은 kernel 의 “real wall-clock” 과 다를 수 있음.
- nsys — 시각적 timeline 강력. 그러나 file 사이즈가 큼 (1초당 100MB). long run 에는 sampling.
- ncu — kernel 별로 측정 비용 큼. 한 번에 하나의 metric 만 측정 가능 — 같은 kernel 을 N 번 다른 metric 으로 측정.
- NVBench — Georgii 의 작품. C++ benchmark 작성 → 자동화. publication 수준 결과의 표준.
profiler 자체의 영향
profiler 가 켜져 있으면 — 측정 대상의 시간이 약간 변함. 일부 workload 에서는 ~5% 변화. 정확한 throughput 이 필요하면 — profiler off 상태에서 같은 코드 한번 더 측정해 sanity check.
§ 06noise 와 신뢰구간· 통계 처리
한 번 잰 숫자는 거짓말이다 — N 번의 분포로 본다
한 번 측정한 시간 — 99% 정확하지 않다. clock jitter, OS scheduler, thermal, neighbour process — 모두 작은 noise 를 더함. 그래서 N 회 측정의 분포 로 봐야 한다. 평균만 보지 말고 — min, p50 (median), p99, std.
표준 통계 처리
(1) N=100~1000 회 측정. (2) outlier 제거 (top/bottom 5%). (3) median 또는 trimmed mean 보고. (4) std/min/max 도 같이. (5) 두 baseline 비교 시 — 신뢰구간 겹치는지 확인. 안 겹치면 “유의미한 차이”.
measurement 3
12.1 ms · outlier?
median (N=100)
10.2 ± 0.3
두 코드를 비교할 때 — “A 가 10.2ms, B 가 9.8ms 라서 B 가 4% 빠르다” 같은 결론은 위험. 만약 std 가 ±0.4ms 라면 두 분포가 겹친다 → 차이 유의미하지 않을 수 있음. 적어도 신뢰구간 겹침을 확인해야 함.
“숫자 하나로 결론 내리지 말 것 — 분포가 결론이고 평균이 그 분포의 한 점일 뿐이다.” 학습 노트 · 재구성
GPU clock lock
noise 의 가장 큰 단일 변수 — clock boost. nvidia-smi -lgc base_clock 로 GPU clock 을 base 에 lock 하면 측정값의 std 가 절반 이하로 떨어짐. publication 수준 측정의 표준. (단, 이 클럭에서의 측정값은 production 의 boost 클럭 측정값과 다르다는 점 명시 필요.)
§ 07micro vs macro benchmark· 두 다른 게임
한 kernel 의 측정과 전체 모델의 측정은 다른 패턴
micro-benchmark — 한 kernel 의 측정. cache, launch overhead, isolated env. 깊은 분석에 좋음. macro-benchmark — 전체 학습/추론의 wall-clock. 진짜 production 시간. 두 측정의 결과가 일치하지 않을 수 있음.
μ
micro-benchmark한 kernel · 격리 환경
한 kernel 의 forward 를 1000 번 호출. cache 통제, clock lock, NVBench. 결과: “이 kernel 은 100μs 다”. 다른 kernel 과의 interaction 안 봄.
M
macro-benchmark전체 모델 · 실 환경
학습 step 또는 추론 query 의 전체. 실제 cache 상태, 실제 launch sequence, 실제 memcpy. 결과: “이 모델 step 은 50ms 다”. 어느 kernel 이 dominant 인지 분리 어려움.
⚠
두 결과의 불일치흔한 사례
한 kernel 을 micro 에서 30% 빨라지게 했는데 macro 에서 model 시간이 안 빨라짐. 이유 — (1) 그 kernel 이 critical path 가 아니었음, (2) 다른 kernel 과 stream overlap 되어 있었음, (3) cache 상태가 micro 와 다름.
병행 사용
healthy 워크플로 — micro 에서 kernel 을 빠르게 짜고, macro 에서 그 kernel 이 모델 시간에 어떻게 반영되는지 확인. 두 측정이 모두 일치하면 진짜 빠름. 한쪽만 빨라지면 — critical path 또는 cache 의 영향 의심.
§ 08자동화 패턴· harness 디자인
NVBench 가 자동으로 잡아주는 것들
benchmark harness 의 일을 손으로 짜는 것은 — 매번 같은 함정을 만든다. NVBench 같은 framework 가 그 함정을 자동 처리. Georgii 의 큰 contribution.
자동 워밍업
N 회 워밍업 후 측정 시작. N 은 timing variance 가 stable 해질 때까지 자동 결정.
자동 측정 횟수
신뢰구간이 desired precision 에 도달할 때까지 추가 측정. 짧은 kernel 은 더 많이, 긴 kernel 은 적게.
cache flush
매 call 사이에 L2 cache flush. memory-bound workload 의 honest 측정.
GPU clock lock
자동으로 base clock 에 lock 시도. permission 있어야 함.
timer 정확도
CUDA Event 사용. ~0.5μs 정확도. 짧은 kernel 의 측정에 적합.
여러 input 자동
N 개의 다른 input 을 round-robin. cache 자연 evict.
통계 보고
median, mean, std, min, max. CSV / JSON 출력. publication 수준.
parametric sweep
여러 input size, BLOCK_SIZE 등을 자동 sweep. 결과 grid 로 출력.
PyTorch 사용자의 대안
NVBench 는 C++ benchmark 위주. PyTorch 환경에서는 — torch.utils.benchmark.Timer, triton.testing.do_bench. 비슷한 자동화. 두 도구 모두 — 자동 워밍업, 자동 N 결정, 통계 출력.
“정직한 measurement 의 가장 큰 trick — 기존의 검증된 harness 를 쓰는 것. 손으로 짜면 함정에 빠진다.” 학습 노트 · 재구성
§ 09publication 기준· paper / blog 의 bar
“이 kernel 이 빠르다” 를 발표하기 전 체크리스트
새 kernel 을 paper / blog / repo 에 발표할 때 — measurement 가 충분히 정직한가의 체크리스트. 강의의 가장 실용적 결론.
- warmup 5+ 회 — 첫 cold call 은 측정에 안 들어감.
- 측정 100+ 회 — 통계 처리. median 또는 trimmed mean.
- std / 신뢰구간 보고 — 평균만 발표하지 말 것.
- baseline 동일 환경 측정 — 자기 코드만 잘 측정하고 비교 baseline 은 default 셋팅이면 안 됨. 같은 GPU, 같은 driver, 같은 측정 도구.
- cache flush 또는 fresh input — memory-bound kernel 은 cache 잔재가 결정적.
- GPU clock 명시 — boost 인지 base 인지. base lock 권장.
- compile flags 명시 — nvcc 의 optimization level. 다르게 컴파일하면 다른 결과.
- dead-code 검증 — kernel 출력을 어디든 사용. 컴파일러가 통째로 제거 안 했는지.
- 여러 input shape — 한 shape 에서만 빠르고 다른 shape 에서는 안 그럴 수 있음. 표 또는 그래프로.
- 다른 GPU 에서 재측정 — A100 에서 빠른 게 H100 에서 느릴 수 있음.
- reproducibility — 코드 + 측정 스크립트 + 환경 (driver, CUDA 버전) 명시. 다른 사람이 재현 가능.
정직한 발표의 표준
“우리 kernel 이 baseline 보다 1.8× 빠르다 (median, N=1000, std=±0.05ms, A100 base clock, fresh input)” — 이 정도 모양이 publication 수준. 단순히 “1.8× faster” 만 발표하는 게 아니라 — 측정 셋업 명시.
“speedup 의 숫자는 항상 측정 셋업과 함께 기억된다 — 셋업이 빠진 숫자는 의미 없는 숫자다.” 학습 노트 · 재구성
§ 10기억할 메모와 자료· key takeaways
다시 열었을 때 5분 안에 손에 잡혀야 할 것
8가지 함정
host time, no warmup, cache 잔재, dead-code, clock jitter, neighbour process, launch overhead, async memcpy.
표준 워밍업
5~10 회 워밍업 + sync, 그 다음 100~1000 회 측정. cold start 가 중요한 워크로드는 별도 측정.
cache flush
memory-bound 워크로드는 매 call 사이 cache flush 또는 fresh input round-robin.
측정 도구
Event (가벼움), nsys (timeline), ncu (deep), NVBench (publication), torch.utils.benchmark (Python).
통계 처리
median + std. outlier 5% 제거. 신뢰구간 비교. clock lock 으로 std 절반 감소.
micro vs macro
micro 가 빨라져도 macro 가 안 빨라지는 경우 — critical path 아님 또는 stream overlap 문제.
dead-code elim 회피
kernel 결과를 어디든 사용. volatile 변수에 쓰기, side effect 만들기.
publication 체크리스트
warmup, N, std, baseline 동일 환경, cache, clock, compile flags, dead-code, input shapes, GPU, repro.
§ 11다른 강의로 이어지는 길· connections
같은 자리를 다른 각도에서 다루는 강의들
§ 12열린 질문· open questions
원본 자막 실패로 비워둔 자리들
- Georgii 의 구체적 “tales” — 강의 제목이 “Tales” 인 만큼 구체적 일화가 여러 개 있을 가능성. 본 노트는 일반적 함정으로 재구성. 원본 영상 확인 필요.
- NVBench 의 정확한 자동화 디테일 — 어떤 알고리즘으로 N 을 결정하는지, cache flush 방법은 어떻게 구현되는지.
- “흥미로운 buggy 측정” 사례 — 강의에서 실제 “이 paper 의 측정이 깨졌다” 같은 구체적 사례를 들었을 가능성.
- HPC 분야의 베스트 프랙티스 — Georgii 가 NVIDIA 에서 cuDNN/CUB 를 다루므로 HPC kernel 의 표준 측정에 더 깊은 디테일 가능성.
- multi-GPU benchmark — NCCL 시간 포함, GPU 간 sync 의 타임라인. 강의에서 다뤘는지.
- cloud / shared GPU 의 문제 — neighbour process 의 영향 통제 방법. lambdalabs 같은 환경에서.
- Hopper 의 새 측정 도구 — H100 의 hardware counter 가 새로 노출된 자리들.
- energy 측정 — perf/watt — 측정의 또 다른 차원. 강의에서 언급되었는지.
검증 메모
본 노트의 모든 함정 목록과 측정 도구 비교는 — Georgii 의 NVBench 와 NVIDIA cuda-samples 의 best practice 문서, Andreas Köpf / Sebastian Aaltonen 의 공개 가이드를 종합한 재구성. 강의 자체에서 구체적 사례 (특정 paper, 특정 kernel 의 깨진 측정) 를 들었다면 — 그 디테일은 본 노트에 빠져 있음. 영상을 직접 본 사람이 노트 보강 권장.