Nsight Compute 분석
NVIDIA Nsight Compute(ncu)는 개별 CUDA 커널이 왜 느린지, 무엇을 최적화해야 하는지 판단하기 위해 프로파일링합니다. GPU 처리량을 이론적 최대치의 백분율(Speed of Light / SOL%)로 측정하여 체계적인 병목 분류와 목표 지향적 최적화를 가능하게 합니다.
사용 시기
다음과 같은 상황에서 이 스킬을 사용하세요:
- 트리거: 사용자가 CUDA 커널을 프로파일링하거나,
ncu출력을 분석하거나,.ncu-rep보고서를 해석하거나, GPU 커널 성능을 최적화하려는 경우 - 증상: 커널이 예상보다 느리게 실행되거나, GPU 사용률이 낮거나, 컴퓨트 바운드 vs 메모리 바운드 분류가 필요하거나, 점유율 문제가 있는 경우
- 키워드: "ncu", "nsight compute", "SOL%", "speed of light", "커널 프로파일링", "compute-bound", "memory-bound", "latency-bound", "점유율", "루프라인", "워프 스톨", "캐시 적중률", "ncu-rep"
다음에는 이 스킬을 사용하지 마세요:
- 시스템 수준 프로파일링 (Nsight Systems /
nsys사용) - CUDA API 추적 또는 CPU-GPU 타임라인 분석 (
nsys사용) - 프로파일링 없는 GPU 모니터링 (
nvidia-smi사용)
요구 사항
| 의존성 | 버전 | 비고 |
|---|---|---|
| CUDA Toolkit | >=11.0 | ncu 포함 |
ncu 바이너리 | 드라이버가 지원하는 버전 | Nsight Compute 2026.3은 CUDA 13 호환 드라이버 필요. 또는 $NCU 환경 변수 설정 |
| NVIDIA GPU | Turing 이상 | Volta는 Nsight Compute 2025.2 이하 필요 |
권한: ncu는 sudo, CAP_SYS_ADMIN, 또는 컨테이너에서 --privileged가 필요할 수 있습니다. ncu -v는 버전만 출력하며, 권한 부족 시 첫 번째 커널 프로파일링에서 ERR_NVGPUCTRPERM 오류가 발생합니다.
원칙
데이터 무결성
이는 데이터 기반 분석 시스템입니다. 제시하는 모든 숫자는 신뢰할 수 있는 출처가 있어야 합니다. 예외 없이 다음 규칙을 따르세요:
- 해석하기 전에 인용하세요. ncu 출력에서 메트릭을 제시할 때는 항상 실행한 실제 ncu 명령어와 관련 원시 출력(CSV 라인, 메트릭 값)을 숫자 결론을 내리기 전에 보여주세요.
- 절대 메트릭을 조작하지 마세요. ncu가 실패하거나, 예상치 못한 출력을 반환하거나, 실행할 수 없는 경우 명시적으로 말하세요. 그럴듯한 숫자를 만들어내지 마세요. 정직한 "프로파일링 실패"가 조작된 데이터보다 낫습니다.
- 모든 값에 출처를 명시하세요. 인용하는 각 메트릭(SOL%, 지속 시간, 점유율, 처리량)에 대해 독자가 제시한 원시 ncu 출력의 특정 라인까지 추적할 수 있어야 합니다.
SOL% 정신 모델
Speed of Light(SOL%)는 커널이 GPU 이론적 최대치에 얼마나 가깝게 실행되는지 측정합니다:
- Compute SOL% (
Compute (SM) Throughput) = 가장 바쁜 SM 유닛의 처리량을 최대치의 %로 표시 - Memory SOL% (
Memory Throughput) = 가장 바쁜 메모리 유닛(DRAM, L2, L1, 공유 메모리)의 처리량을 최대치의 %로 표시
더 높은 메트릭이 일반적으로 병목 유형을 나타냅니다. 이를 주요 분류 신호로 사용하세요. 둘 다 높을 수 있습니다: 커널이 해당 알고리즘에 대해 하드웨어 한계에 가까운 경우입니다.
분류 임계값
| Compute % | Memory % | 병목 유형 | 다음 단계 |
|---|---|---|---|
| >60 | <40 | Compute-bound | ComputeWorkloadAnalysis 섹션 |
| <40 | >60 | Memory-bound | MemoryWorkloadAnalysis 섹션 |
| <40 | <40 | Latency-bound | LaunchStats + Occupancy 섹션 |
| 40-60 | 40-60 | 균형 | 세부 섹션으로 더 깊이 프로파일링 |
| >60 | >60 | 하드웨어 한계에 근접 | 알고리즘 변경; 두 워크로드 섹션 확인 |
추가 신호:
- 지속 시간 <10us이고 많은 실행이 있는 경우 -> Launch-overhead bound (먼저 nsys 사용)
- 둘 다 <40%이지만 점유율 >50%인 경우 -> Instruction-bound (InstructionStats 확인)
SOL% 성능 수준
| SOL% | 수준 | 조치 |
|---|---|---|
| >80% | 우수 | 사소한 튜닝만 |
| 60-80% | 양호 | 목표 지향적 최적화 |
| 40-60% | 보통 | 상당한 최적화 필요 |
| <40% | 불량 | 주요 재작업 필요 |
섹션 우선 프로파일링
항상 대량 --set 수집 대신 목표 지향적인 --section 플래그를 사용하세요. 개별 섹션이 더 빠르고 정밀합니다. 광범위한 탐색이 필요할 때만 --set basic 또는 --set detailed로 확장하세요.
ncu vs nsys
| 도구 | 범위 | 오버헤드 | 목적 |
|---|---|---|---|
| nsys | 시스템 수준 | 5-10% | 최적화할 커널 찾기 |
| ncu | 커널 수준 | 10-100배 느림 | 커널이 느린 이유 이해 |
먼저 nsys를 사용하여 GPU 시간 기준 상위 커널을 식별한 다음, 해당 특정 커널에 대한 심층 분석에 ncu를 사용하세요.
워크플로
요청에 따라 경로를 선택하세요:
- 지식 질문 (사용할 메트릭, --section vs --set, 커널 필터링 방법): 아래 원칙, 명령어 참조, 참고 자료에서 직접 답변하세요. ncu를 실행하지 마세요.
- 빠른 진단 (병목 분류, SOL% 확인): 1단계만 수행. 사용자가 더 원하면 확장하세요.
- 특정 진단 (뱅크 충돌, 레지스터 압력, 점유율): 빠른 SOL% 확인(1단계) 후 2단계의 해당 섹션으로 바로 이동하세요.
- 심층 분석 (상세 보고서, 최적화 권장 사항): 전체 1-5단계 수행. 최종 응답에 모든 주요 메트릭(SOL%, 지속 시간, 점유율)이 포함된 완전한 구조화된 보고서를 제시하세요. 보고서를 여러 메시지에 나누거나 간단한 요약으로 대체하지 마세요.
0단계: ncu 확인
ncu -v
# 또는: $NCU -v
찾을 수 없으면 CUDA 툴킷이 설치되었는지 확인하거나 NCU 환경 변수를 바이너리 경로로 설정하세요.
1단계: SOL% 진단
항상 SpeedOfLight로 시작하여 병목을 분류하세요:
ncu --section SpeedOfLight --csv \
--kernel-name regex:"KERNEL" \
--launch-skip 5 --launch-count 3 \
-- COMMAND
출력에서 Compute (SM) Throughput과 Memory Throughput을 읽으세요. 위 임계값을 사용하여 분류하세요.
2단계: 목표 섹션으로 확장
1단계 분류를 기반으로 섹션을 추가하세요:
| 분류 | 추가할 섹션 |
|---|---|
| Compute-bound | ComputeWorkloadAnalysis |
| Memory-bound | MemoryWorkloadAnalysis |
| Latency-bound | LaunchStats, Occupancy |
| 워프 스톨 | WarpStateStats, SchedulerStats |
| 명령어 분석 필요 | InstructionStats |
지연 바운드 커널 진단 시 항상 LaunchStats와 Occupancy를 포함하세요. 이는 레지스터 압력, 공유 메모리 제한, 블록 크기 문제를 드러냅니다.
예시 -- 메모리 바운드 심층 분석:
ncu --section SpeedOfLight --section MemoryWorkloadAnalysis --csv \
--kernel-name regex:"embedding_lookup" \
--launch-count 3 \
-- python script.py
예시 -- 컴퓨트 바운드 심층 분석:
ncu --section SpeedOfLight --section ComputeWorkloadAnalysis --csv \
--kernel-name regex:"gemm|nvjet" \
--launch-count 3 -- python script.py
예시 -- 점유율 조사:
ncu --section SpeedOfLight --section LaunchStats --section Occupancy --csv \
--kernel-name regex:"small_kernel" \
-- python script.py
3단계: 루프라인 분석 (선택 사항)
컴퓨트 vs 메모리 균형을 시각적으로 이해하기 위한 것입니다. 루프라인 섹션은 차트입니다: CLI에서는 --print-details all을 사용할 때만 인쇄되며, 차트 뒤의 숫자(달성 및 최대 작업 및 트래픽)를 나열합니다. 차트를 보려면 -o로 보고서를 저장하고 Nsight Compute UI에서 여세요.
ncu --section SpeedOfLight_RooflineChart --print-details all \
--kernel-name regex:"KERNEL" -- COMMAND
정밀도별 계층적 루프라인의 경우:
# FP16 커널
ncu --section SpeedOfLight_HierarchicalHalfRooflineChart --print-details all \
--kernel-name regex:"KERNEL" -- COMMAND
# 텐서 코어 커널
ncu --section SpeedOfLight_HierarchicalTensorRooflineChart --print-details all \
--kernel-name regex:"KERNEL" -- COMMAND
해석: ridge point 왼쪽의 커널 = memory-bound; 오른쪽 = compute-bound; 두 지붕선 아래에 있으면 = latency/occupancy 문제. references/roofline-analysis.md 참조.
4단계: 해석 및 최적화
- SOL% 분류에서 지배적인 병목 식별
references/bottleneck-guide.md에서 상세 분석 및 최적화 전략 확인- 가장 영향력이 큰 최적화부터 적용
- 재프로파일링하여 개선 확인 및 병목 이동 감지
5단계: 검증
최적화 후 동일한 커널을 재프로파일링하세요:
ncu --section SpeedOfLight --csv \
--kernel-name regex:"optimized_kernel" \
--launch-count 3 \
-- python optimized_script.py
비교: 처리량 %가 증가했습니까? 지속 시간이 감소했습니까? 병목 유형이 변경되었습니까?
JIT 컴파일 커널 프로파일링 (Triton/cuTile/CuTeDSL)
JIT 컴파일 커널은 첫 번째 호출 시 자동 튜닝을 트리거합니다. 실제 실행을 분리하세요:
- 먼저 웜업: JIT 컴파일 및 자동 튜닝을 완료하기 위해 커널을 3-5회 실행한 후
torch.cuda.synchronize(). - 프로파일러 마커 사용:
cudaProfilerStart()/cudaProfilerStop()으로 측정 영역을 감싸세요. --profile-from-start off사용하여 ncu가 마커 영역만 캡처하도록:
# 웜업 (JIT + 자동 튜닝)
for _ in range(5):
result = kernel(inputs)
torch.cuda.synchronize()
# 안정 상태만 프로파일링
torch.cuda.cudart().cudaProfilerStart()
for _ in range(3):
result = kernel(inputs)
torch.cuda.synchronize()
torch.cuda.cudart().cudaProfilerStop()
ncu --profile-from-start off --section SpeedOfLight --csv \
--kernel-name regex:"target_kernel" \
--launch-count 3 -- python script.py
대안: --launch-skip N을 사용하여 자동 튜닝 실행을 건너뛰세요. NVTX 범위 및 재생 모드 대안은 references/advanced-profiling.md를 참조하세요.
프로그래밍 방식 보고서 분석
.ncu-rep 또는 .ncu-repz 파일에서 ncu_report Python 모듈(Nsight Compute 설치의 extras/python/에 위치)을 사용하여 메트릭을 추출하세요:
import ncu_report
ctx = ncu_report.load_report("report.ncu-rep")
for rng in ctx:
for action in rng:
name = action.name()
# SpeedOfLight의 "Compute (SM) Throughput" 및 "Memory Throughput"
compute = action["sm__throughput.avg.pct_of_peak_sustained_elapsed"].as_double()
memory = action["gpu__compute_memory_throughput.avg.pct_of_peak_sustained_elapsed"].as_double()
duration = action["gpu__time_duration.sum"].as_uint64()
if compute > 60:
classification = "compute-bound"
elif memory > 60:
classification = "memory-bound"
else:
classification = "latency-bound"
print(f"{name}: {classification} (compute={compute:.1f}%, mem={memory:.1f}%, {duration}ns)")
전체 API(IContext, IRange, IAction, IMetric 클래스)는 references/python-report-api.md를 참조하세요.
출력 형식
CSV 출력 (스크립팅 및 자동 분석용):
ncu --csv --section SpeedOfLight --kernel-name regex:"KERNEL" -- COMMAND
ncu --csv --page raw --section SpeedOfLight -- COMMAND # 모든 메트릭 평면
기본 CSV는 메트릭당 한 행입니다. 열에는 ID(프로파일링된 실행), Kernel Name, Section Name, Metric Name, Metric Unit 및 Metric Value가 포함됩니다. 규칙 결과는 Rule Name, Rule Description 및 Estimated Speedup을 채우는 행입니다. Metric Name은 섹션 간에 반복될 수 있으므로(Memory Throughput은 SpeedOfLight에서는 %이고 MemoryWorkloadAnalysis에서는 byte/s임) Section Name으로도 필터링하세요. --page raw는 실행당 한 행, 메트릭당 한 열, 단위의 두 번째 행을 제공합니다. 값은 기본 단위(ns, hz, %)입니다.
프로파일링 중 ncu의 ==PROF== 라인과 애플리케이션 자체 출력이 CSV와 stdout을 공유합니다. 깨끗한 CSV를 얻으려면 보고서를 작성하고 다시 읽으세요:
보고서 파일 (추후 분석용):
ncu -o report.ncu-rep --section SpeedOfLight -- COMMAND
ncu --import report.ncu-rep --csv # 메트릭당 한 행
ncu --import report.ncu-rep --csv --page raw # 실행당 한 행
-o에 확장자를 제공하세요: 확장자가 없으면 Nsight Compute 2026.3 이상은 zstd 압축된 report.ncu-repz를 쓰고, 이전 버전은 report.ncu-rep를 씁니다. --import와 ncu_report는 둘 다 읽습니다.
주요 메트릭 행(Metric Name 값):
| 메트릭 이름 | 섹션 | 의미 |
|---|---|---|
Duration | SpeedOfLight | 실행 시간 |
Compute (SM) Throughput | SpeedOfLight | 최대 컴퓨트의 % |
Memory Throughput | SpeedOfLight | 가장 바쁜 메모리 유닛 최대치의 % |
SM Frequency | SpeedOfLight | 프로파일이 실행된 클록 |
Achieved Occupancy | Occupancy | 활성 워프 / 최대 워프 (%) |
성공 지표:
- 출력에 SOL% 값이 있음 -> 프로파일링 성공
- 지속 시간 값이 합리적임(0 또는 극도로 크지 않음)
--launch-count > 1인 경우 여러 실행이 캡처됨
예제
예제: GEMM 커널 분류
Blackwell의 cuBLAS GEMM 커널은 nvjet_*로 명명되므로 두 이름을 모두 매칭하세요:
ncu --section SpeedOfLight --csv \
--kernel-name regex:"gemm|nvjet" \
--launch-skip 5 --launch-count 3 \
-- python train.py
B200에서 FP16 4096x4096 행렬 곱셈에 대한 출력(열과 행은 축약됨):
"ID",...,"Kernel Name",...,"Section Name","Metric Name","Metric Unit","Metric Value",...
"0",...,"nvjet_sm100_hsh_128x256_64x6_2x2f_2cta_h_bz_NNT",...,"GPU Speed Of Light Throughput","Memory Throughput","%","38.38",
"0",...,"nvjet_sm100_hsh_128x256_64x6_2x2f_2cta_h_bz_NNT",...,"GPU Speed Of Light Throughput","Duration","ns","97600",
"0",...,"nvjet_sm100_hsh_128x256_64x6_2x2f_2cta_h_bz_NNT",...,"GPU Speed Of Light Throughput","Compute (SM) Throughput","%","77.11",
해석: compute-bound (77.1% 컴퓨트, 38.4% 메모리). 다음 단계: --section ComputeWorkloadAnalysis로 텐서 코어 사용량 확인.
예제: 메모리 바운드 임베딩 커널 진단
PyTorch의 임베딩 룩업은 vectorized_gather_kernel을 실행하므로 이 이름도 매칭하세요:
ncu --section SpeedOfLight --section MemoryWorkloadAnalysis --csv \
--kernel-name regex:"gather|index|embedding" \
--launch-count 3 -- python train.py
출력에서 L1/L2 캐시 적중률을 확인하세요: 낮은 적중률은...