제1부 GPU 아키텍처
1.10 루프라인·메모리바운드 분석
1.10.1 이 장에서 배우는 것 / 선수 지식
1.9에서 대역폭·용량이라는 물리 자원을 봤다. 이 장은 그 대역폭을 성능 천장으로 바꾸는 도구 — 산술 강도와 루프라인 모델로 임의의 커널이 연산 바운드인지 메모리 바운드인지를 종이·연필로 판정하고, 메모리 바운드의 약(융합·재사용·정밀도)을 정한다. "내 GPU는 1000 TFLOPS인데 왜 커널이 그 10%도 못 쓰나?"에 답하는 장이다.
배우고 나면 할 수 있어야 하는 것:
- 임의 연산의 산술 강도(I = FLOP/byte) 를 계산하고 루프라인에서 바운드를 판정한다.
- 피크 TFLOPS와 실제 달성치의 괴리를 메모리 대역폭으로 설명한다.
- 메모리 바운드 커널의 약이 "코어 가속"이 아니라 "융합·재사용·정밀도 축소로 I 높이기"임을 안다.
선수 지식: 1.2(산술 강도 개요), 1.8(병합), 1.9(대역폭·용량). FMA=2 FLOP, FP16=2B 등 바이트 계산을 전제로 쓴다.
용어: 이 장에서 산술 강도는 혼동을 피해 I(intensity)로 쓴다("AI"는 인공지능). 피크 수치는 산수 시범용 가정.
1.10.2 산술 강도와 임계 강도
두 천장(연산·메모리, 1.9)을 비교하려면 공통 단위가 필요하다. 그것이 산술 강도 I:
$I =$ 연산량(FLOP) / 데이터 이동량(byte) [FLOP/byte]
I는 "메모리에서 1바이트를 끌어올 때 그 바이트로 몇 번 계산하는가"다. I가 크면(재사용 많음) 연산 바운드 쪽, 작으면(한 번 쓰고 버림) 메모리 바운드 쪽. GPU마다 둘이 갈리는 임계 강도 I*:
- $I^* =$ 피크 FLOP/s / 피크 byte/s [FLOP/byte]
- $=$ "이 GPU에서 연산과 메모리가 균형을 이루는 산술 강도"
내 커널의 I가 I*보다 작으면 메모리 바운드, 크면 연산 바운드다.
왜 중요한가. 메모리 바운드 커널을 아무리 빠른 텐서코어로 가속해도(텐서코어 2배로 늘려도) 빨라지지 않는다 — 병목은 HBM이다. 최적화 노력을 잘못된 곳에 투입하지 않으려면 먼저 바운드를 판정해야 한다.
1.10.3 루프라인 모델 — 두 천장을 한 그래프에
루프라인은 가로축 산술 강도 I(로그), 세로축 달성 성능 P(FLOP/s, 로그)로 그린다. 천장은 두 선의 최솟값:
- 경사 부분(I < I*): 천장 = BW × I. 강도가 커질수록 성능이 선형으로 오른다 — 데이터가 병목. 더 빠른 코어는 무의미, 재사용을 늘려 I를 키우는 것이 유일한 약.
- 수평 부분(I > I*): 천장 = 피크 FLOP/s. 강도를 더 키워도 코어가 한계 — 코어가 병목. 텐서코어·정밀도·점유율로 코어 효율을.
- 꺾이는 점 I* = 피크÷BW. 현대 데이터센터 GPU에서 FP16/텐서코어 기준 I*는 대략 수백 FLOP/byte 수준이라(피크가 byte 대비 워낙 커서), 재사용이 적은 연산은 거의 다 경사 영역 = 메모리 바운드가 된다.
내 커널을 점 하나로 찍으려면 x=커널 I, y=달성 P가 필요하다. 점이 경사선에 가까우면 대역폭을 잘 쓰는 메모리 바운드, 수평선 아래 멀리 있으면 비효율(점유율·런치 오버헤드·분기)을 의심한다. 측정은 2.14(Nsight).
1.10.4 워크스루 — 바운드 판정 레시피
수치 예제는 방법을 보이기 위한 것이다. 가정 피크: 1000 TFLOP/s(FP16 텐서코어), BW 2 TB/s → I* = 1000e12 ÷ 2e12 = 500 FLOP/byte.
- ① 데이터 이동량 $=$ (읽는 원소 + 쓰는 원소) $\times$ 원소 바이트 (HBM을 실제로 오가는 것만)
- ② 연산량(FLOP) $=$ 그 커널의 부동소수 연산 수 ($\mathrm{FMA} = 2\,\mathrm{FLOP}$)
- ③ $I = \mathrm{FLOP} / \mathrm{byte}$ → $I^*$와 비교. $I<I^*$ 메모리 바운드, $I>I^*$ 연산 바운드
- 달성 가능 성능 $\approx \min($피크, $\mathrm{BW} \times I)$
1.10.4.1 예제 A — 원소별 연산(벡터 덧셈·활성화)
C[i] = A[i] + B[i], FP16(2B). 원소마다 A·B 읽기(4B) + C 쓰기(2B) = 6B, 연산 1 FLOP.
- $I = 1 / 6 \approx 0.17\,\mathrm{FLOP/byte}$. $0.17 \ll 500$ → 강하게 메모리 바운드.
- 달성 성능 $\approx \mathrm{BW} \times I = 2\mathrm{e}12 \times 0.17 \approx 0.33\,\mathrm{TFLOP/s}$ (피크 대비 0.03%)
이 커널을 텐서코어로 옮기거나 코어를 늘려봤자 소용없다. 유일한 가속: 인접 연산과 융합(fusion) 해 같은 데이터로 더 많은 일을 시켜 I를 키우는 것. add→mul→activation을 세 커널로 실행하면 매번 HBM 왕복이지만, 하나로 융합하면 1회 읽기·1회 쓰기로 → I가 3배. cuDNN 그래프 융합(3.3)·FlashAttention(3.6)이 노리는 효과다.
1.10.4.2 예제 B — GEMM(행렬곱), 재사용이 큰 연산
$C[M,N] = A[M,K] \times B[K,N]$, FP16. FLOP = 2·M·N·K, 데이터(최소) = (MK + KN + MN)×2B.
- $M=N=K=4096$: $\mathrm{FLOP} = 2 \cdot 4096^3 \approx 1.37\mathrm{e}11$. $\mathrm{byte} = 3 \cdot 4096^2 \cdot 2 \approx 1.01\mathrm{e}8$.
- $I = 1.37\mathrm{e}11 / 1.01\mathrm{e}8 \approx 1365\,\mathrm{FLOP/byte}$. $1365 > 500$ → 연산 바운드.
핵심은 GEMM의 I가 타일 크기에 따라 달라진다는 것. 1365는 "각 원소를 HBM에서 한 번만 읽는" 이상적 재사용 가정값이다. 실제로는 온칩(공유 메모리·레지스터)에 타일을 적재해 재사용한다(2.7). 타일 변 길이 T면 I ≈ T(정사각 근사). 작은 K(K=64인 얇은 GEMM, LLM 디코딩의 배치=1 GEMV)에서는 I가 뚝 떨어져 메모리 바운드로 미끄러진다. 그래서 같은 "행렬곱"이라도 학습(큰 배치·큰 K)은 연산 바운드, 추론 디코딩(배치·K 작음)은 메모리 바운드가 된다 — 이 경계가 5.8의 인플라이트 배칭이 노리는 지점(배치를 키워 GEMV를 GEMM으로 만들어 I↑).
1.10.4.3 예제 C — LLM 디코딩 한 토큰의 대역폭 하한
오토리그레시브 디코딩에서 토큰 1개를 내려면 모델 가중치 전체를 최소 한 번 HBM에서 읽어야 한다(배치=1). 파라미터 P개, 정밀도 b바이트면:
- 토큰당 최소 데이터 $\approx P \cdot b\,\mathrm{byte}$. 토큰당 최소 시간 $\approx P \cdot b / \mathrm{BW}$.
- 상한 처리량(tokens/s) $\approx \mathrm{BW} / (P \cdot b)$.
- 예: $P=70\mathrm{e}9$(70B), $b=2$(FP16), $\mathrm{BW}=2\,\mathrm{TB/s}$:
- 토큰당 데이터 $= 1.4\mathrm{e}11\,\mathrm{B} = 140\,\mathrm{GB}$. 시간 $\ge 0.07\,\mathrm{s}$ → 상한 $\approx 14\,\mathrm{tokens/s}$ (배치=1).
이 수는 연산이 아무리 빨라도 못 넘는 대역폭 천장이다. 함의 둘:
- 양자화로 b를 줄이면 직접 빨라진다. FP16(2B)→FP8(1B)→INT4(0.5B)로 가면 토큰당 이동량이 절반·1/4로 줄어 상한 처리량이 그만큼 오른다(정확도 트레이드오프는 1.13·5.4).
- 배치를 키우면 가중치 읽기를 여러 요청이 분담한다 — 한 번 읽은 가중치로 B개 토큰을 계산하니 가중치 측 I가 B배. 그래서 서빙은 배치를 키워 대역폭 효율을 끌어올린다(5.8·5.13). 단 배치를 키우면 KV 캐시가 커져 용량 천장에 부딪힌다(1.9).
1.10.5 중간 점검 (체크포인트)
- [ ] "피크 TFLOPS와 실제의 괴리"를 한 문장으로 설명한다: 커널 I가 I*보다 작으면 성능은 BW×I로 묶이고 달성치는 피크의 (I/I*)배에 그친다 — 비효율이 아니라 물리적 천장이다.
- [ ] 임의 커널의 데이터 이동량·FLOP을 세어 I를 계산하고 바운드를 판정한다.
- [ ] 메모리 바운드면 최적화 방향이 "코어 가속"이 아니라 "융합·재사용·정밀도로 I 높이기"임을 안다.
증상: 실측이 루프라인 경사선보다도 낮다 → 대역폭조차 못 채움. 비병합(1.8)·낮은 점유율(1.6)·런치 오버헤드(작은 커널 다발 → 2.11 Graphs)·과도한 분기 의심. 큰 GEMM인데 메모리 바운드처럼 보임 → 타일링이 깨졌거나 K가 작거나 비병합(2.14).
1.10.6 트레이드오프 — 메모리 바운드 커널을 빠르게 하는 ①②③
- 연산 융합(fusion). 인접 element-wise·정규화·활성화를 한 커널로 묶어 중간 텐서 HBM 왕복 제거. I를 (융합 연산 수)배로. → 가장 보편적·효과 큰 레버. cuDNN 그래프(3.3)·torch.compile·TensorRT 레이어 융합(5.2)이 자동화.
- 데이터 재사용 극대화(타일링·온칩 스테이징). 공유 메모리·레지스터에 타일을 적재해 HBM 재방문을 줄임(2.7). GEMM·컨볼루션·어텐션의 핵심. FlashAttention(3.6)은 어텐션을 타일링해 S² 중간 행렬을 HBM에 안 쓰고 스트리밍한다.
- 정밀도 축소(b↓). FP32→FP16/BF16→FP8→INT4로 원소 바이트를 줄여 이동량을 직접 줄임. 메모리 바운드에서 거의 선형 이득. 정확도·캘리브레이션 트레이드오프는 1.13·5.4.
견해. 새 커널을 만나면 순서는 (a) 바운드 판정 → (b) 메모리 바운드면 융합·재사용·정밀도부터, 연산 바운드면 텐서코어·점유율부터. 판정 없이 코어 최적화에 뛰어드는 것이 가장 흔한 시간 낭비다.
비엔비디아 정직성: 루프라인·산술 강도는 벤더 중립이다 — 어떤 가속기(AMD·TPU·NPU)에도 피크 FLOP/s와 대역폭을 넣어 자기 I*를 구하면 같은 방식으로 판정한다. 차이는 절대 수치와 소프트웨어 성숙도(융합·라이브러리 자동화)에 있지 모델 자체에 있지 않다.
1.10.7 흔한 함정
- 피크 TFLOPS를 곧 실제로 오해. 피크는 데이터 이동 비용이 0이라는 가정의 상한. 마케팅 TFLOPS/TOPS는 특정 정밀도·희소성(1.13~1.14) 가정을 둔다.
- FMA를 1 FLOP로 세거나 정밀도 바이트를 틀림. GEMM FLOP=2·M·N·K. FP16=2B, FP8/INT8=1B. 한 자리 틀리면 판정이 뒤집힌다.
- 데이터 이동량을 "총 접근"으로 부풀리거나 캐시 무시로 과소. byte는 "HBM을 실제로 오가는 양". L2 재사용 트래픽은 빼고, 비병합 초과 트래픽은 더한다. 상·하한을 범위로 보면 안전.
- 메모리 바운드 커널에 텐서코어를 갈아 넣음. 경사 영역에선 코어를 늘려도 천장이 안 오른다. 융합·정밀도부터.
- 작은 커널 다발의 런치 오버헤드를 대역폭 문제로 오진. 수많은 작은 커널은 런치 지연·동기화가 병목일 수 있다 → CUDA Graphs(2.11)·융합. Nsight로 구분(2.14).
1.10.8 연습문제
가정 GPU: 피크 1000 TFLOP/s(FP16), BW 2 TB/s (I*=500).
연습 1. FP32(4B) 텐서의 GELU y=GELU(x)(원소당 ~10 FLOP). 원소당 이동량·I·바운드를 판정하고 가속법을 제시하라.
정답
x 읽기 4B + y 쓰기 4B = 8B, ~10 FLOP → I = 10/8 = 1.25. 1.25 ≪ 500 → 메모리 바운드. 가속: 앞뒤 연산과 융합, 또는 FP16/BF16(2B)으로 이동량 절반. 코어 가속 무의미.
연습 2. GEMM M=N=K=1024 FP16. (a) 이상적 재사용에서 FLOP·byte·I·바운드. (b) 4096과 비교해 I 변화 설명.
정답
(a) FLOP=2·1024³≈2.15e9, byte=3·1024²·2≈6.29e6, I≈342 → 이상적 가정에서도 살짝 메모리 바운드(경계 근처). (b) I≈변 길이에 비례 → 4096에서 I≈1365 연산 바운드. 클수록 재사용↑→I↑→연산 바운드. 작은 GEMM·작은 K(디코딩 GEMV)는 메모리 바운드.
연습 3. 130B 모델을 INT4(0.5B/param), 배치=1, BW=3 TB/s로 디코딩. 토큰당 최소 시간·상한 tokens/s, FP16(2B) 대비 배수.
정답
INT4 토큰당 = 130e9×0.5 = 65 GB, 시간 ≥ 65/3000 ≈ 0.0217 s → 상한 ≈ 46 tokens/s. FP16: 260 GB, ≈11.5 tokens/s. INT4가 4배(2B→0.5B). 대역폭 천장 상한이며 실제는 더 낮다.
연습 4. mul→add→tanh 세 커널(각 입력 읽고 결과 씀, FP16)을 하나로 융합하면 HBM 트래픽과 I는 몇 배 변하나? (중간 텐서 크기 = 입출력)
정답
비융합 ≈ 입력1읽기+중간2쓰기+중간2읽기+출력1쓰기 ≈ 6 텐서-패스. 융합 ≈ 입력1읽기+출력1쓰기 ≈ 2 텐서-패스. 트래픽 ≈ 1/3, I ≈ 3배. 메모리 바운드면 실행 시간도 ≈ 1/3. 융합이 메모리 바운드의 최대 레버인 이유.
연습 5 (개방형). Nsight 실측이 루프라인 경사선(BW×I)의 절반에 그쳤다(대역폭조차 못 채움). 원인 후보 3가지와 확인·교정법을 NV-NN과 함께.
정답(예시)
① 비병합 접근 → 초과 HBM 트래픽. 확인: Nsight 메모리 차트·sector/request(2.14). 교정: 병합 레이아웃(1.8). ② 낮은 점유율로 지연 은닉 실패. 확인: occupancy·stall 사유(1.5~1.6·2.14). 교정: 블록·레지스터·공유메모리 조정. ③ 런치 오버헤드·작은 커널 다발. 확인: 커널 지속시간 분포(2.14). 교정: 융합·CUDA Graphs(2.11). 그밖에 과도한 분기·atomic 경합.
1.10.9 요약 · 다음 장
- 모든 커널의 실행 시간은 연산·메모리 천장 중 느린 쪽이 정한다. 잇는 수가 산술 강도 I=FLOP/byte, 갈림길은 임계 강도 I*=피크÷BW다.
- AI 워크로드의 많은 부분이 메모리 바운드다. 피크의 한 자릿수 %만 내는 것은 비효율이 아니라 물리적 천장이다.
- 메모리 바운드의 약은 코어가 아니라 융합·재사용(타일링)·정밀도 축소로 I를 높이는 것. 연산 바운드라야 텐서코어·점유율이 효과를 낸다.
제1부 메모리 블록(1.7~1.10)이 끝났다. 다음 장: 1.11 텐서코어 I — MMA·워프 수준 행렬곱. 연산 천장 쪽을 끌어올리는 하드웨어 — 행렬 곱-누산(MMA)이 어떻게 동작하는지 본다.