제2부 CUDA 프로그래밍
2.13 프로파일링 I — Nsight Systems
1.10에서 종이와 연필로 "이 커널은 메모리 바운드인가"를 추정했다면, 프로파일링은 그 추정을 실측으로 확정한다. 도구는 둘 — Nsight Systems(nsys)는 시스템 전체를 시간축에 펼쳐 "어디서 시간이 새는가"를, Nsight Compute(ncu)는 커널 하나를 현미경으로 "왜 느린가"를 답한다. 이 장은 시스템 수준(nsys), 다음 장(2.14)이 커널 수준(ncu)이다.
2.13.1 이 장에서 배우는 것 / 선수 지식
배우는 것:
- 언제
nsys를, 언제ncu를 쓰는지 판단한다(시스템 vs 커널 병목). nsys profile로 타임라인을 수집하고, 커널 갭·복사 직렬화·CPU 병목·오버랩 실패를 읽는다.- 측정 → 가설 → 수정 → 재측정 사이클의 첫 단계(시스템 병목 식별).
- 워밍업·클럭 변동 함정.
선수 지식: 1.5(워프 stall), 1.8(병합), 1.10(루프라인·메모리 바운드), 2.10(스트림·오버랩). 커널 수준 진단은 2.14.
환경 주의(S-5): 도구 이름·CLI·섹션 명칭은 시점·버전 의존.
nsys는 CUDA Toolkit 동봉. 아래 출력은 예시(개략)다. 정확한 옵션은nsys --help·공식 문서(A.3).
2.13.2 왜 측정인가 — 추측의 비용
GPU 최적화에서 가장 비용이 큰 실수는 잘못된 병목을 고치는 것이다. 메모리 바운드 커널에 텐서코어를 갈아 넣어도 안 빨라진다(1.10). 코드만 보면 "FLOP 많으니 연산 바운드겠지"라는 추정이 자주 틀린다. 실제 병목은 비병합 접근·낮은 점유율·런치 오버헤드·복사 직렬화처럼 소스에 안 보이는 곳에 있다.
핵심 규율: 먼저 시간이 어디로 가는지 보고(nsys), 가장 비용이 큰 커널만 골라 왜 느린지 본다(ncu). 모든 커널을 ncu로 파고들 필요는 없다 — 전체 시간의 80%를 먹는 한두 커널이 보통 답이다.
2.13.3 두 도구, 두 척도
Nsight Systems (nsys) | Nsight Compute (ncu) | |
|---|---|---|
| 척도 | 시스템 전체, 시간축 | 커널 하나, 하드웨어 카운터 |
| 답하는 질문 | "시간이 어디로 가나" | "이 커널은 왜 느린가 / 무엇에 바운드인가" |
| 보는 것 | 커널·복사 타임라인, CPU 스레드, API, NVTX, 갭·오버랩 | 점유율, SOL, 메모리 차트, 루프라인, warp stall |
| 오버헤드 | 낮음(샘플링·트레이싱) | 높음 — 커널을 여러 번 replay(수십~수백 배 느려질 수 있음) |
| 먼저? | 항상 먼저 | nsys로 핫 커널을 고른 다음 |
결정 규칙: 반복이 느림/GPU가 유휴인 듯/복사 의심/CPU 병목 → nsys. 특정 커널이 핫이고 왜 느린지/메모리·연산 바운드 판정 → ncu(2.14).
비엔비디아 정직: 프로파일링 사고법은 벤더 중립 — "타임라인으로 시스템 병목, 카운터로 커널 병목"은 AMD(rocprof·Omniperf)·인텔(VTune)에도 적용된다. 루프라인·점유율·산술 강도는 가속기 종류와 무관. 차이는 도구 이름·메트릭 명칭.
2.13.4 Nsight Systems — 타임라인으로 읽는 시스템 병목
2.13.4.1 무엇이 타임라인에 그려지나
nsys는 실행 시간을 가로축으로, 여러 레인(track) 을 세로로 쌓는다.
읽는 법: SM(커널) 레인이 비어 있는 구간 = GPU 유휴 시간이다. 그 갭이 왜 생겼는지 위·아래 레인에서 찾는다. 갭 직전이 복사면 "복사 대기", 갭에 CPU API 호출이 길게 이어져 있으면 "CPU 병목·동기화 거품", 갭 없이 커널이 빽빽하면 GPU는 잘 쓰는 중(그땐 ncu로 커널 내부, 2.14).
2.13.4.2 수집 — nsys profile
nsys profile -o report ./my_app --some-arg # 기본: .nsys-rep 리포트 생성
# -t: 추적 대상(CUDA API·NVTX 주석·OS 런타임; cudnn,cublas 추가 가능) · --stats=true: 콘솔 요약
nsys profile \
-t cuda,nvtx,osrt \
-o report --stats=true \
./my_app
긴 실행은 -c cudaProfilerApi로 코드의 cudaProfilerStart/Stop 구간만, 또는 --capture-range/--delay로 워밍업 이후 정상 구간만 취한다(2.13.5). GUI로 report.nsys-rep를 열면 타임라인, CLI는 nsys stats report.nsys-rep로 표 요약.
2.13.4.3 콘솔 요약 읽기 (예시·개략)
[CUDA Kernel Summary] (시간 내림차순)
Time% Total(ms) Instances Avg(us) Name
62.0 310.5 1000 310.5 fused_norm_kernel ← 핫
21.0 105.2 1000 105.2 gemm_tile_kernel
9.0 45.1 1000 45.1 softmax_kernel
[CUDA Memory Operations]
Time% Total(ms) Operation
70.0 88.0 [CUDA memcpy HtoD]
30.0 37.7 [CUDA memcpy DtoH]
해석: fused_norm_kernel이 GPU 커널 시간의 62% → ncu로 파고들 첫 후보(2.14). HtoD/DtoH 복사가 상당하면(특히 매 반복) 전송이 직렬화 중일 수 있으니 타임라인에서 복사·커널의 오버랩을 확인한다.
2.13.4.4 타임라인에서 찾는 4대 시스템 병목
| 시스템 병목 | 타임라인 증상 | 해법 |
|---|---|---|
| ① 커널 갭 (GPU 기아) | SM 레인이 자주 빈다. 작은 커널 다발 사이 런치 오버헤드·CPU 거품 | 융합·CUDA Graphs(2.11) |
| ② 복사 직렬화 | 복사 엔진과 SM이 번갈아만 일하고 안 겹침 | pinned + 비동기 복사 + 스트림 오버랩(2.10) |
| ③ CPU 병목 | CPU 레인은 빽빽한데 SM 레인은 듬성. 데이터 준비·파이썬 오버헤드 | 입력 파이프라인(3.8) |
| ④ 동기화 거품 | cudaDeviceSynchronize·동기 복사·이벤트 대기가 긴 정체 | 불필요한 동기화 제거, 비동기화 |
NVTX 주석(nvtxRangePush/Pop 또는 PyTorch torch.cuda.nvtx)을 코드에 넣으면 타임라인에 의미 있는 구간 이름("forward"·"backward"·"dataloader")이 찍혀 어느 단계가 기아를 일으키는지 단번에 보인다.
2.13.5 측정을 망치는 함정 (타이밍)
- 워밍업 누락. 첫 커널 실행은 JIT 컴파일(PTX→SASS, 2.12)·캐시 콜드·라이브러리 핸들 초기화·CUDA 컨텍스트 생성으로 느리다. 첫 몇 회를 버리고 정상 상태를 측정.
nsys는--capture-range/-c cudaProfilerApi로. - 클럭 변동. GPU는 전력·온도에 따라 클럭을 높이고 내린다(boost·thermal throttle). 측정 중 흔들리면 같은 커널이 매번 다른 시간을 낸다. 재현 가능한 측정은 클럭 고정(
nvidia-smi --lock-gpu-clocks, 권한 필요)하거나 여러 번 측정해 분산을 본다. - 시스템 병목을 커널 최적화로 풀려 함.
nsys에서 GPU가 기아 상태인데(커널 갭·CPU 병목·복사 직렬화) 커널 내부만 파면 답이 안 나온다. GPU가 빽빽이 일하는 것이 먼저 — 그 다음 커널 효율. 순서는 항상nsys→ncu. - Release 빌드·실제 입력으로 안 잼. 디버그 빌드(
-G)는 최적화가 꺼져 SASS가 다르다. 초소형 입력은 캐시에 다 들어가 메모리 바운드를 못 드러낸다. 배포 빌드·실제 크기로.
2.13.6 중간 점검 (체크포인트)
- [ ]
nsys(시스템)와ncu(커널) 중 무엇을 먼저 쓸지, 증상으로 판단한다. - [ ] 타임라인에서 SM 레인의 갭을 찾고, 갭 원인(복사 대기·CPU 병목·동기화)을 위·아래 레인에서 짚는다.
- [ ] 콘솔 요약에서 핫 커널(시간 80%)을 골라
ncu후보로 삼는다. - [ ] 워밍업·클럭 변동을 측정에서 통제한다.
자가 점검: 학습 한 스텝이 느린데 GPU 사용률이 낮다 → 무엇을 먼저? (nsys — GPU가 유휴인 시스템 병목 의심, 타임라인부터.)
2.13.7 흔한 함정
- 추측으로 최적화 시작. 측정 없이 병목을 정하면 잘못된 곳을 고친다.
nsys로 시간이 어디 가는지부터. - 모든 커널을
ncu로 파기. 80%를 먹는 한두 개만. 그 식별이nsys의 역할. - NVTX 미사용으로 단계 식별 어려움.
forward/backward/dataloader주석을 넣으면 어느 단계가 기아를 일으키는지 단번에.
2.13.8 연습문제
연습 1 (도구 선택). 각 증상에 nsys·ncu·이벤트 타이밍 중 무엇을 먼저: (a) 학습 한 스텝이 느린데 GPU 사용률 낮음 (b) 이 커널이 핫인데 메모리 바운드인지 모름 (c) 방금 융합한 커널이 빨라졌는지만 빨리 확인 (d) 복사가 커널과 안 겹치는 듯.
정답
(a) nsys — GPU가 유휴인 시스템 병목, 타임라인부터. (b) ncu --set full — SOL·루프라인(2.14). (c) CUDA 이벤트 타이밍 — 절대 시간 차이만 낮은 비용으로. (d) nsys — 복사·SM 레인 오버랩은 타임라인에서.
연습 2 (타임라인 해석). nsys 타임라인에서 SM 레인이 작은 커널들 사이로 규칙적 빈틈, CPU 레인은 launch로 꽉 찼다. 진단명과 해법은?
정답
런치 오버헤드로 인한 GPU 기아(CPU-bound). 소형 커널 다발이라 CPU가 GPU를 못 따라간다. 해법: 커널 융합 또는 CUDA Graphs(2.11)로 launch 시퀀스를 한 번에.
연습 3 (워밍업 함정). 같은 커널을 100회 호출하며 (a) 1회차 (b) 2~100회 평균 시간을 비교하라. 1회차가 왜 느린가? 무엇을 버려야?
정답
1회차는 JIT 컴파일(2.12)·콜드 캐시·컨텍스트/핸들 초기화로 느리다. 앞 몇 회를 워밍업으로 버리고 정상 상태 평균을 보고. 1회차를 포함해 평균 내면 짧은 벤치에서 크게 왜곡된다.
2.13.9 요약 · 다음 장
- 프로파일링은 측정 → 가설 → 수정 → 재측정 사이클. 추측으로 병목을 정하지 말 것.
nsys(Nsight Systems) 는 시간축으로 시스템 병목(커널 갭·복사 직렬화·CPU 병목·동기화 거품)을 보여준다. 항상 먼저, 핫 커널을 고른다.- SM 레인의 갭 = GPU 유휴 시간. 갭 원인을 위·아래 레인에서 찾고, NVTX로 단계를 식별한다.
- 워밍업·클럭 변동을 통제하고, 시스템 병목을 커널 최적화로 풀려 하지 말 것.
다음 장: 2.14 프로파일링 II — Nsight Compute. nsys로 고른 핫 커널 하나를 SOL·메모리 차트·점유율·warp stall·루프라인으로 해부해 "무엇에 바운드인가"를 5분 안에 판정한다.