gpumode · 강의 아카이브
《GPU Mode》 L037 2024 · NOV High priority transcript · available

Introduction to SASS & GPU Microarchitecture

PTX 가 NVIDIA 의 “virtual ISA” 라면, SASS 는 진짜 칩 위에서 도는 머신코드. NVIDIA 가 공식 문서를 안 내는 이 layer 를 어떻게 읽고, 그게 왜 마지막 30% 의 성능을 결정하는지 — Arun Demeure 가 cuobjdump 화면 위에서 한 줄씩 따라가며 깐다. SM 의 issue slot 과 functional unit, ILP 의 의미, warp scheduler 의 세 가지 stall 원인, 그리고 “Triton 도 결국 SASS 까지 내려간다” 의 진짜 의미.

SASS PTX SM uarch warp scheduler ILP cuobjdump issue slot scoreboard
A
Speaker
Arun Demeure
github.com/ademeure · GPU uarch researcher
강의 번호
L037
스피커
Arun Demeure
학습 우선순위
High · 정독
다시 볼 때
cuobjdump 같이
§ 01강의가 풀려는 문제· 왜 SASS 까지 보는가

“Triton 으로 짠 게 왜 cuBLAS 보다 30% 느린가” 의 답이 보통 SASS 안에 있다

대부분의 GPU 프로그래밍은 PTX 까지만 본다 — 그게 NVIDIA 가 공식 문서를 내주는 마지막 layer 니까. 그런데 진짜 칩 위에서는 SASS 가 돈다. PTX 와 SASS 사이에는 NVIDIA 의 후처리 컴파일러 가 끼어 있고, 그게 instruction reordering, register 재할당, scoreboard 결정을 다 한다.

Arun 의 핵심 입장 — “Triton 으로 짠 좋은 코드도, 직접 쓴 CUDA 도, 결국 같은 SASS 로 컴파일된다. 차이를 결정하는 건 그 SASS 의 모양.” Triton 이 cuBLAS 의 80% 까지 따라잡았는데도 마지막 20% 가 안 풀리는 이유는 PTX 단계에서는 안 보이고, SASS 를 까봐야 보인다.

강의의 인지적 frame

“PTX 만 보고 성능을 추론하지 마라” — 이게 강의의 가장 큰 메시지. PTX 의 instruction 한 개가 SASS 에서 한 개로 사상되지 않는다. PTX 가 깔끔해 보여도 SASS 에서 spill, redundant load, scheduling stall 이 들어 있을 수 있다. SASS 가 진실.

“PTX 는 abstraction — 진짜 일이 어디서 어떻게 도는지 알려면 SASS 를 봐야 한다. 그게 마지막 30% 를 결정한다.”Arun Demeure · 강의 도입부

그래서 강의 끝에 손에 잡혀야 할 자산은 — (1) SASS 한 줄을 읽는 법, (2) SM 의 issue slot 과 functional unit 의 매핑이라는 mental model, (3) warp scheduler 가 한 cycle 마다 무슨 결정을 하는가, (4) cuobjdump / godbolt 로 자기 커널을 까보는 워크플로.

§ 02PTX vs SASS 의 위치· 컴파일 사다리

한 사다리 위 5단계 — Triton 도, 직접 CUDA 도, 같은 길을 내려간다

L0 · 소스 CUDA C++ / Triton DSL / OpenAI Triton사람이 손으로 짜는 단계 __global__ void / @triton.jit
L1 · IR LLVM IR / Triton-MLIR각 컴파일러의 중간 형태. tile op / scalar op 분리. .ll / .mlir
L2 · PTX virtual ISANVIDIA 가 공식 문서로 내주는 마지막 layer. arch-agnostic 한 척한다 (실제론 sm_xx 별로 일부 다르긴 함). .ptx · 사람이 어느 정도 읽음
L3 · SASS SM 별 ISA실제 hardware 가 fetch/decode 하는 머신코드. NVIDIA 가 문서를 안 줌. ptxas 의 후처리 컴파일러가 만든다. cuobjdump --dump-sass
L4 · scheduled SASS SASS + scheduling control bits각 instruction 옆에 latency hint, scoreboard, reuse cache 같은 메타정보가 붙어 있다. 이게 진짜 hardware 가 보는 형태. .B0/.B1 wait, .R cache flag

위에서 가장 흥미로운 건 L4. SASS 의 매 instruction 옆에 “이 결과는 4 cycle 뒤에 ready 됨” 이나 “이 register 는 cache 에서 읽어라” 같은 control bits 가 붙어 있다. 이게 hardware scheduler 가 stall 결정을 하는 근거. NVIDIA 의 후처리 컴파일러가 이 control bits 를 결정 — 그래서 같은 PTX 에서 다른 SASS 가 나올 수 있고, scheduling 이 다른 만큼 latency 가 다르다.

PTX 가 거짓을 말할 수 있다

강의에서 Arun 이 짚은 한 줄 — “PTX 의 instruction 수와 SASS 의 instruction 수가 다르다. PTX 한 줄이 SASS 여러 줄로 펴지기도 하고, PTX 여러 줄이 SASS 하나로 합쳐지기도 한다.” 그래서 PTX 만 보고 “register 사용량이 적다” 같은 결론을 내리면 SASS 에서 다르게 나올 수 있다.

§ 03SM 별 ISA 차이· sm_80 ~ sm_90

같은 algorithm 도 SM 마다 다른 instruction 으로 컴파일된다

2017
sm_70 · Volta
  • tensor core 도입
  • indep thread sched
  • cp.async 없음
2020
sm_80 · Ampere
  • BF16 / TF32
  • cp.async
  • L2 cache control
2022
sm_89 · Ada
  • FP8 (E4M3, E5M2)
  • 4090 / L40
  • tensor core 4세대
2022
sm_90 · Hopper
  • TMA · WGMMA
  • thread block cluster
  • distributed shmem

이 표는 단순한 hardware 이력이 아니다 — 같은 PTX 가 어느 sm_xx 으로 컴파일되느냐에 따라 SASS 가 통째로 달라진다는 사실의 근거. 예를 들어 sm_80 에서 cp.async 가 한 instruction 이지만, sm_70 에서는 일반 load 두 개로 쪼개져서 register 트래픽이 두 배가 된다.

Triton 의 sm 별 자동 dispatch

Triton 은 같은 tl.dot 코드를 어느 sm 으로 컴파일하느냐에 따라 다른 SASS instruction 으로 매핑한다 — sm_80 이면 mma.sync, sm_90 이면 wgmma. 그래서 같은 Triton 코드의 SASS 가 GPU 마다 다른 모양이 된다는 사실이 한 번 손에 잡혀야 한다.

강의에서 Arun 이 든 구체적 예 — H100 에서 thread block cluster 가 도입되며 distributed shared memory 가 가능. 같은 PTX 의 ld.shared 가 cluster scope 에서는 다른 SM 의 SMEM 까지 본다. 이런 사실은 PTX manual 에는 한 줄로 적히지만, SASS 에서는 새 instruction (LDGSTS 의 변종) 으로 펴진다.

§ 04warp scheduler 의 결정· ready warp pool

한 cycle 마다 — “지금 issue 할 수 있는 warp 가 있는가?” 의 답이 곧 throughput

SM 안의 warp scheduler 는 매 cycle ready warp pool 을 본다. 그 pool 이 비어 있으면 그 cycle 의 functional unit 이 비워진다. 이게 occupancy 가 중요한 이유의 본질.

Ampere SM 한 개 (sm_80)

warp scheduler4 개
FP32 unit / cycle64
tensor core / cycle4 (각 4 sub-slice)
LSU (load/store)16
MUFU (sin/exp)4
최대 거주 warp64

cycle 별 결정 시퀀스

1 · ready warp 들 식별scoreboard ok
2 · 각 warp scheduler 가 한 warp 선택round-robin / age
3 · warp 의 다음 inst issueFMA/LD/TC 한 곳
4 · functional unit 점유latency = 4 cycle?
5 · 결과 ready 시 scoreboard 갱신B0…B5
stall 이유scoreboard / no-warp / dispatch

강의에서 Arun 이 명시한 한 줄 — “warp 가 64 개 거주해도, 그중 ready 인 게 매 cycle 충분하지 않으면 occupancy 만 높고 utilization 은 낮다.” 이 차이가 NCU 의 “Achieved Active Warps Per Scheduler” 와 “Warp Cycles Per Issued Instruction” 두 metric 의 차이.

stall 의 세 종류

(a) Long Scoreboard — 메모리 latency 로 register 가 ready 가 안 됨. (b) Short Scoreboard — TC / MUFU 결과 대기. (c) Dispatch Stall — issue slot 이 비어서 그 cycle 못 issue. (a) 가 가장 흔하고, 보통 더 많은 warp 또는 더 작은 footprint 로 푼다.

§ 05issue slot · functional unit· FMA · MUFU · LSU · TC

한 cycle 안에서 여러 unit 이 동시에 도는 이유

SM 한 개에는 여러 functional unit 이 있고, 그 unit 들이 동시에 돈다. FP32 FMA 가 도는 cycle 에 LSU 가 다른 warp 의 load 를 처리할 수 있고, tensor core 가 또 다른 warp 의 MMA 를 진행 중일 수 있다 — 이게 GPU 가 “bandwidth bound” 와 “compute bound” 를 동시에 다룰 수 있는 이유.

FIG · 한 cycle 동안 SM 의 여러 unit 이 동시에idealized
FMA pipe
warp 3 · FFMA
warp 7 · FFMA
empty
warp 12 · FFMA
warp 1 · FFMA chain
LSU (load)
warp 5 · LDG long lat
warp 9 · LDG
empty
tensor core
warp group · WGMMA (long async)
wait
한 cycle 안에 3 개 unit 이 동시에 도 있다. 좋은 코드는 이 unit 들을 모두 점유 — 한 unit 이 비면 그 비례만큼 throughput 손해.

강의에서 Arun 이 강조한 사실 — tensor core 가 매우 빠르긴 한데, 그게 “FFMA 가 비어 있다” 의 의미가 아니다. softmax 같은 elementwise 연산은 FMA 와 MUFU pipe 위에서 도는데, 그게 tensor core 와 동시에 돌아야 attention 이 효율적으로 도는 (§ FA3 의 ping-pong).

“GPU 의 좋은 코드는 한 unit 을 100% 채우는 게 아니라 여러 unit 을 동시에 돌리는 코드다. 그게 ILP 의 본질.”Arun Demeure
§ 06SASS 읽기 트릭· cuobjdump · godbolt

두 도구만 있으면 SASS 를 까볼 수 있다

강의의 실용 부분. SASS 를 읽으려면 — (1) cuobjdump --dump-sass 로 binary 에서 추출, 또는 (2) godbolt.org 의 NVCC 모드로 소스 → SASS 한 화면. 후자가 학습용으로 압도적으로 빠르다.

# Triton 으로 짠 fp32 squared sum 의 SASS 일부 (sm_80, A100)
# godbolt 에서 그대로 본 모양. 색은 임의 — instruction 영역만.

  /*0050*/ LDG.E.SYS R6, [R2.64+0x0] ;          // global load · long lat
  /*0058*/ LDG.E.SYS R7, [R2.64+0x4] ;          // global load · long lat
  /*0060*/ FMUL      R8, R6, R6 ;                  // x * x
  /*0068*/ FMUL      R9, R7, R7 ;                  // y * y
  /*0070*/ FFMA      R10, R8, R9, R10 ;            // acc += x*x + y*y
  /*0078*/ @!P0 BRA   `(.L_x_0) ;                    // loop branch
  /*0080*/ STG.E.SYS [R12.64], R10 ;              // store result

읽을 때 손에 잡히는 사실들 — LDG.E.SYS 가 global load (E = 64-bit address). FFMA 가 fused multiply-add (한 cycle throughput). @!P0 BRA 가 predicated branch. /*0050*/ 가 instruction 의 byte offset (8 byte 씩 증가하니 instruction 당 8 byte 인 sm_70+ 패턴).

한 줄 패턴 모음

R0…Rn 일반 register, P0…P6 predicate. SR special register (threadIdx 등). S2R special-to-register (idx load). UR uniform register (sm_75+). BAR.SYNC barrier. NOP 진짜 비어 있는 cycle.

godbolt 의 NVCC compiler 옵션 — -arch=sm_80 -O3 --ptxas-options=-v — 가 SASS 를 같이 보여준다. Triton 도 같은 trick — TRITON_DEBUG=1 또는 ~/.triton/cache/ 의 디렉토리 안에 *.cubin + *.ptx 가 같이 떨어진다.

§ 07스케줄링이 보이는 자리· .B0 wait, scoreboard

SASS 의 옆에 붙은 메타정보 한 줄이 latency hint 다

진짜 hardware 가 보는 SASS 는 instruction + control bits. cuobjdump 는 기본으로 control bits 를 안 보여주는데, --dump-sass-noindex 류 옵션 또는 별도 도구로 까보면 — 매 instruction 옆에 “이 결과는 N cycle 뒤 ready”, “register cache 에서 읽어라”, “scoreboard B0 까지 기다려라” 같은 hint 가 박혀 있다.

구조적으로 hardware 는 — (1) long-latency instruction (LDG, MMA) 을 issue 할 때 어떤 scoreboard slot (B0..B5) 에 결과를 기록할지 결정, (2) 그 결과를 읽는 instruction 은 그 slot 이 ready 될 때까지 wait. 이 wait 가 명시적으로 제어 bit 로 박혀 있어 hardware scheduler 가 단순화된다.

왜 이게 중요한가

NVIDIA 의 후처리 컴파일러가 이 control bits 를 영리하게 채워 넣는다. 같은 PTX 를 다른 sm_xx 으로 컴파일하면 SASS 의 control bits 가 달라진다. 그게 latency-bound 한 코드에서 결정적인 차이를 만든다 — 명시적 wait 가 길게 박힌 SASS 와, 영리하게 짧은 wait 만 박힌 SASS 사이.

강의에서 Arun 이 구체적 사례로 든 — backward 가 forward 보다 느려 보일 때, PTX 는 비슷한데 SASS 의 scoreboard wait 분포가 다르다. PTX 만 보고는 안 보이는 차이.

§ 08ILP — 마지막 30%· latency × throughput

한 thread 가 여러 일을 “동시에” 시키게 만드는 트릭

강의의 가장 비실용적이지만 가장 흥미로운 부분. ILP (Instruction-Level Parallelism) — 한 thread 안에서도 독립적인 instruction 들이 동시에 issue 될 수 있다. 그래서 thread 수가 많지 않아도 throughput 이 산다.

예시 — A100 의 FFMA pipe 의 latency 가 4 cycle, throughput 이 1/cycle. 만약 한 thread 가 매 4 cycle 마다 dependent 한 FFMA 한 개만 발행하면 throughput 의 25% 만 사용. 같은 thread 가 4개 독립 FFMA 를 매 cycle 하나씩 발행하면 — 같은 thread 만으로도 4배 이득.

naive (dependent chain)25% peak
ILP=2 (2-way unroll)50% peak
ILP=4 (4-way unroll)~95% peak
multi-warp (occupancy)~80% peak

강의에서 Arun 이 짚은 한 줄 — “40 cycle latency / 10 cycle 마다 한 개 throughput 인 unit 은 적어도 40 개 thread/warp 가 동시에 inflight 해야 latency 를 숨긴다.” 이게 occupancy + ILP 의 합. ILP 를 충분히 짜면 occupancy 를 낮춰도 됨.

unform register 의 자리

sm_75+ 의 uniform register (UR) — warp 안 32 thread 가 같은 값을 가지는 register 를 별도 file 로 둔다. compiler 가 이걸 잘 활용하면 일반 register 의 압박이 줄어든다. PTX 에서는 직접 noted 안 되고 SASS 에서만 보임. “같은 일을 누가 하면 좋을까” 의 결정 단위가 GPU 에서 점점 다양해진다.

§ 09마이크로아키 변천· Volta → Hopper

하나의 SM 안에 “비동기 단위” 가 점점 많아지는 방향

SM 의 진화를 한 줄로 요약하면 — 점점 더 많은 일이 비동기로 issue 가능해진다. Volta 의 tensor core 도, Ampere 의 cp.async 도, Hopper 의 TMA / WGMMA / cluster 도 모두 같은 방향. 한 thread 가 “큰 일을 시작하고 다른 일을 한다” 가 본질.

“각 세대마다 새 instruction 이 들어오는데, 그 instruction 의 진짜 의미는 SASS 까지 봐야 잡힌다 — 같은 PTX 가 어떻게 다른 SASS 가 되는지.”학습 노트
§ 10기억할 메모와 코드· cuobjdump 실습

다시 열었을 때 5분 안에 손에 잡혀야 할 것

PTX → SASS
두 layer 사이에 NVIDIA 의 후처리 컴파일러. PTX 한 줄이 SASS 한 줄로 사상되지 않는다.
control bits
SASS 옆 latency hint, scoreboard slot, register cache. 진짜 hardware 가 보는 형태.
warp scheduler
SM 마다 4 개. ready warp pool 에서 한 cycle 마다 한 warp 의 다음 inst 를 issue.
stall 3 종류
long scoreboard (메모리), short scoreboard (TC/MUFU), dispatch (issue slot 비움).
functional unit 동시성
FMA · LSU · TC · MUFU 가 한 cycle 안에 동시. 좋은 코드는 여러 unit 을 같이 점유.
cuobjdump
cuobjdump --dump-sass binary. godbolt 의 NVCC 모드도 같은 일을 학습용으로 빠르게.
ILP
한 thread 안 독립 instruction 의 동시 발행. occupancy 가 낮아도 ILP 로 latency 를 숨김.
uniform register
sm_75+ . warp 안 모든 thread 가 같은 값. 일반 register 압박 완화.
Toolgodbolt.org — NVCC 모드로 소스 → SASS
Toolcuobjdump --dump-sass binary.cubin · CUDA toolkit 동봉

손에 새기기 — 실습 시퀀스

  1. godbolt 위에서 한 줄 커널__global__ void k(float*x){x[0]=x[0]*x[0];}. NVCC sm_80 으로 컴파일해서 SASS 한 화면. LDG → FMUL → STG 패턴 직접 본다.
  2. 같은 코드, 다른 sm — sm_70 / sm_80 / sm_90 으로 컴파일. instruction 시퀀스의 차이를 직접 비교. cp.async / tma 가 어디서 들어오는지.
  3. Triton 의 cubin 까보기 — Triton 커널을 한 번 돌리면 ~/.triton/cache/ 에 cubin. cuobjdump --dump-sass 로 까서 자기 Triton 코드의 진짜 모양 확인.
  4. PTX vs SASS line count — 같은 커널의 PTX 줄 수와 SASS 줄 수 비교. 보통 비슷하지 않다는 사실 손에 잡기.
  5. NCU 의 stall reasons — long scoreboard, short scoreboard, dispatch stall 의 비율을 자기 커널에서 측정. 가장 큰 stall 의 원인을 SASS 안에서 찾아 매핑.
  6. ILP 실험 — dependent FFMA chain vs 4-way unroll 의 throughput 차이를 직접 측정. ILP 의 mental model 을 숫자로 박는다.
§ 11다른 강의로 이어지는 길· connections

SASS 의 시각이 시리즈 안에서 다시 등장하는 자리

§ 12열린 질문· open questions

다음에 다시 들었을 때 직접 검증해야 할 것들

검증 메모

이 노트의 모든 SASS instruction 형태는 강의 화면을 재구성한 예시. LDG.E.SYS, FFMA 같은 mnemonic 은 sm_80 기준이고, sm_90 은 다른 변종이 있다. 자기 GPU 에서 직접 컴파일해서 본 SASS 가 진실.

← Lecture 036 CUTLASS & FA3 Lecture 038 → Low Bit ARM kernels — Scott Roy 가 깐 모바일 GPU/CPU 위 LLM 추론