# Bandwidth Not FLOPS Is the Multi-GPU Bottleneck ![[gpu-bandwidth-bottleneck-pipeline.png]] > [!info] 개소리 다이얼로그 — 회의론자 vs 전문가 > A(전문가) vs B(회의론자, 소프트웨어 엔지니어)가 "왜 멀티 GPU 머신에서 raw compute [[FLOPS\|FLOPS]]가 아니라 컴포넌트 간 대역폭이 병목이냐"를 술자리 톤으로 깐 대화. 정의 나열이 아니라 B가 막히는 지점에서 A가 구체물(파이프라인 `min()`, async I/O, straggler)로 내려가는 패턴. A: 멀티 GPU 머신 8장 박았다고 치자. 각 GPU가 초당 1000 TFLOPS 뽑아. 근데 학습 돌리면 GPU 사용률이 40%야. 나머지 60%? 데이터 기다리느라 논다. 그게 핵심이야. compute는 빨라. data movement가 못 따라와. B: 뭔 개소리야. GPU가 빠른데 왜 놀아? 일을 안 시킨 거 아니야? A: 일을 시켰지. 근데 일감을 못 갖다줬어. 비유하면 이거야: ``` CPU 코어 = 초고속 셰프 (초당 100접시 가능) 재료 = 데이터 재료 나르는 통로 = 메모리/버스 대역폭 셰프 속도: 100접시/초 재료 도착 속도: 10접시/초 ← 여기가 천장 → 실제 처리량: 10접시/초 (셰프는 90% 시간 멍때림) ``` 셰프(FLOPS)를 두 배 빠르게 해도 재료가 10접시/초로 오면 처리량은 그대로 10이야. 못 올라가. B: 잠깐, 그거 그냥 **producer-consumer 큐에서 consumer가 producer보다 빠른 상황** 아니야? 큐가 비어서 consumer가 block되는 거. A: 정확해. 한 끗도 안 틀려. GPU = consumer, 메모리/네트워크 = producer. 큐가 starve되는 거야. `queue.get()`이 blocking이고 큐가 비어 있으면 스레드가 자는 거랑 똑같아. 차이는 단 하나: 여기선 producer가 느린 게 "I/O 대역폭" 때문이라는 거. B: 오케이. 근데 GPU는 자기 메모리(VRAM) 있잖아. HBM인가 그거. 빠르다며. 그럼 왜 막혀? A: 좋은 질문. 대역폭이 한 종류가 아니라서 그래. 데이터가 거쳐가는 길이 여러 구간이고, 구간마다 속도가 달라: ``` 디스크/SSD → ~2-7 GB/s 시스템 RAM → ~50-100 GB/s PCIe (CPU↔GPU) → ~32-64 GB/s (PCIe 4.0/5.0 x16) NVLink (GPU↔GPU) → ~300-900 GB/s HBM (GPU 내부) → ~2000-3300 GB/s ← 제일 빠름 ``` HBM은 빠르지. 근데 데이터가 거기 *도착하기 전*에 PCIe나 네트워크 같은 느린 구간을 지나와야 해. 그게 문제야. B: 아 잠깐. 이거 완전 **네트워크 라우팅에서 end-to-end throughput이 최저 hop bandwidth에 묶이는 거** 아니야? 1Gbps NIC 두 개 사이에 100Mbps 스위치 하나 끼면 그냥 100Mbps 되는 거. A: 그거야. 그게 바로 이 토픽 전체의 한 문장 요약이야. **데이터 경로(data path)의 최저 대역폭 구간이 전체를 게이트한다.** 너 방금 핵심 짚었어. 네트워크에서 weakest link가 throughput 천장이듯, GPU 머신에서도 데이터가 지나는 가장 느린 버스가 천장이야. B: 그럼 이거 그냥 **Amdahl's law** 다른 버전 아니야? 직렬 구간이 전체를 묶는 거. A: 거의. 근데 한 끗 달라. Amdahl은 "병렬화 못 하는 *비율*"이 천장을 정하는 거고, 이건 "직렬로 흐르는 데이터의 *처리율(rate)*"이 천장이야. 더 정확한 모델은 이거야: ``` 파이프라인 처리량 = min(각 stage의 throughput) stage1: load (디스크) 7 GB/s stage2: H2D copy (PCIe) 32 GB/s stage3: compute (HBM+SM) 2000 GB/s → 전체 처리량 = min(7, 32, 2000) = 7 GB/s ``` 병목은 항상 `min()`. compute가 2000이어도 디스크가 7이면 전체가 7이야. 이걸 **roofline** 또는 그냥 **bottleneck stage**라고 불러. B: 그럼 답 뻔하네. 제일 느린 구간만 빠르게 하면 되잖아. 디스크를 NVMe로 바꾸든가. A: 맞아. 근데 함정이 있어. 병목을 고치면 *병목이 옮겨가.* 디스크 7→로 올리면 이제 PCIe 32가 병목이 돼. 그래서 이게 **whack-a-mole**이야. min()의 최솟값을 올리면 두 번째로 작은 게 새 천장이 돼. B: 아 그거 **프로파일링에서 hot path 하나 고치면 다음 hot path가 튀어나오는 거**랑 똑같네. A: 똑같아. `perf` 돌려서 제일 느린 함수 최적화하면 다음 함수가 top에 올라오는 거. 그래서 "어디가 병목인지"를 *측정*하는 게 추측보다 먼저야. nvidia-smi로 GPU util 보고, `dcgm`이나 nsight로 어느 stage가 stall인지 봐. B: 근데 멀티 GPU 얘기였잖아. GPU 한 장이면 그렇다 쳐. 8장이면 왜 더 심해져? A: 여기가 진짜야. 멀티 GPU는 *새로운 데이터 경로*가 생겨. GPU끼리 통신해야 하거든. 왜? 모델이나 데이터가 쪼개져 있어서: ``` data parallel: 각 GPU가 모델 복사본 가짐. batch만 쪼갬. 매 step마다 gradient를 all-reduce (서로 합산). → GPU↔GPU로 gradient 통째로 오감 model parallel: 모델이 GPU에 쪼개짐 (너무 커서). layer 출력을 다음 GPU로 넘김. → 매 forward/backward마다 activation 오감 ``` 이 GPU↔GPU 트래픽이 NVLink(빠름) 위로 가면 다행인데, NVLink 없으면 PCIe로 가거나 *심지어 네트워크*(여러 노드면)로 가. 그 순간 병목이 거기로 박혀. B: 잠깐, all-reduce가 매 step마다 일어난다고? 그거 **분산 시스템에서 매 트랜잭션마다 합의(consensus) 거치는 거**랑 비슷한 거 아니야? 동기화 비용. A: 비슷한데 한 끗 달라. consensus는 "누가 맞냐"를 정하는 거고, all-reduce는 그냥 "다 더해서 모두에게 뿌려"야. 합의 안 해, 그냥 sum. 근데 네가 짚은 동기화 비용은 정확해 — 모든 GPU가 *자기 gradient 다 계산할 때까지 기다렸다가* 한꺼번에 교환해. 그래서 제일 느린 GPU가 나머지를 막아 (straggler). 이것도 일종의 병목인데, 이번엔 compute-side의. B: 그럼 정리하면, 병목이 세 군데서 날 수 있는 거네. (1) 디스크→GPU 먹이는 길, (2) GPU↔GPU 통신, (3) 느린 GPU 하나. A: 정확해. 그리고 셋 다 "compute FLOPS"랑 무관해. FLOPS는 셰프 속도고, 이 셋은 다 *재료 나르는 통로* 문제야. 그래서 벤더들이 NVLink, NVSwitch, InfiniBand에 그 돈을 쏟는 거야. compute 칩보다 *연결*이 더 비싸지는 지점이 와. B: 근데 솔직히 이거 실제로 얼마나 차이나? FLOPS 두 배 vs 대역폭 두 배. A: 숫자로 보여줄게. 트랜스포머 학습 한 step 가정: ``` compute 시간: 100 ms (matmul 등) gradient all-reduce: 80 ms (PCIe로 통신) ───────────────────────────── 총 step 시간: 180 ms (통신이 겹쳐지지 않는다 가정) FLOPS 2배 → compute 50ms → 총 130ms (28% 빨라짐) 대역폭 2배 → 통신 40ms → 총 140ms (22% 빨라짐) 근데 compute를 4배, 8배 키우면? compute 25ms → 총 105ms compute 12ms → 총 92ms ← 통신 80ms가 바닥. 더 못 내려가. ``` 봐. compute를 아무리 키워도 통신 80ms가 hard floor야. 이게 핵심이야. **FLOPS 투자는 수확체감, 병목 버스가 진짜 천장.** B: 아 그러면 통신을 compute랑 *겹치게* 하면 되는 거 아니야? 기다리는 동안 다른 일 시키고. 비동기로. A: 맞아, 그게 실제 해법이야. **overlap**. gradient를 마지막 layer부터 역순으로 계산하면서, 먼저 끝난 layer의 gradient는 *나머지 계산하는 동안* 백그라운드로 보내. ``` naive: [compute 전체] → [communicate 전체] = 100 + 80 = 180 overlap: [compute] └[communicate 겹침] = max(100, 80) = 100 ``` 이거 정확히 **async I/O**야. 디스크 쓰기 기다리지 말고 백그라운드 스레드에 던지고 다음 일 하는 거. `await` 안 걸고 fire-and-forget 했다가 나중에 join. B: 오케이 그건 알겠어. 그럼 결국 병목을 어떻게 찾냐가 문제네. 추측하지 말라며. A: 그래서 **find the narrowest segment**가 이 토픽의 행동 강령이야. 방법은: ``` 1. 각 stage의 이론 대역폭 적어 (스펙시트) 2. 실측 (nsight, dcgm, nvidia-smi dmon) 3. min() 찾아 → 그게 현재 병목 4. 그 구간만 키워 (또는 overlap으로 숨겨) 5. 다시 측정 → 병목 옮겨갔는지 확인 6. 반복 ``` 추측으로 FLOPS 더 사면 셰프만 빨라지고 재료는 그대로야. 돈 날려. B: 정리되네. 셰프가 아무리 빨라도 재료 안 오면 논다. 재료 통로 중 제일 좁은 데가 천장이고, 그거 고치면 천장이 옮겨갈 뿐이다. 그래서 측정해서 제일 좁은 구간을 찾아라. A: 그게 전부야. 그리고 시작 질문으로 돌아가면 — "왜 FLOPS가 아니라 대역폭이 병목이냐?" 답: compute는 싸고 빨라졌는데 data movement는 물리(거리, 핀 수, 전력)에 묶여서 못 따라왔거든. 그래서 현대 GPU 머신에서 비싼 건 칩이 아니라 *칩들 사이의 길*이야. A: SWE 치트시트 — 이미 아는 것 ← 새로 매핑할 것: ``` 멘탈모델 (이미 아는 것) ← 도메인 용어 (새로 매핑) ────────────────────────────────────────────────────────── producer-consumer 큐 starvation ← GPU가 데이터 기다리며 idle network weakest-hop throughput ← data path 최저 대역폭이 천장 min() over pipeline stages ← bottleneck stage = min(throughput) Amdahl's law (비율 아닌 rate 버전) ← 직렬 데이터 흐름의 처리율 천장 perf hot-path whack-a-mole ← 병목 고치면 병목이 옮겨감 async I/O / fire-and-forget ← communication-compute overlap join / barrier ← all-reduce 동기화 지점 straggler (느린 worker가 batch 막음) ← 제일 느린 GPU가 all-reduce 막음 RAM cache vs disk latency 계층 ← HBM > NVLink > PCIe > RAM > SSD 계층 distributed sum (not consensus) ← all-reduce (합의 아닌 합산) profiling before optimizing ← measure→find min→fix→remeasure diminishing returns ← FLOPS 2배 투자 < 병목 버스 2배 ``` 매듭: "왜 멀티 GPU 머신에서 raw FLOPS가 아니라 대역폭이 병목이냐?" — 셰프(compute)는 물리 법칙을 거의 안 타고 빨라졌지만, 재료 나르는 통로(data movement)는 거리·핀·전력에 묶여 못 따라왔다. 그래서 전체 처리량은 항상 데이터 경로에서 가장 좁은 버스의 `min()`에 묶이고, 그걸 찾아 고치는 게(narrowest segment) 게임의 전부다. FLOPS 더 사는 건 멍때리는 셰프를 더 고용하는 짓이다. ## 관련 노트 - [[왜 AI 추론이 엣지로 가는가 (개소리)]] — 이 GPU↔GPU 병목이 왜 training을 한곳(클라우드)에 모으는지, 반대로 inference는 왜 엣지로 흩어지는지로 한 단계 확장. - [[FLOPS]] — 셰프의 속도. 이 노트가 "그게 천장이 아니다"라고 주장하는 바로 그 지표. - [[GPU 아키텍처 (개소리)]] — HBM, NVLink, SIMT 등 여기 등장한 하드웨어 구간들의 멘탈모델. - [[Transformer]] — all-reduce/overlap 예시로 든 학습 워크로드. - [[Power Concentrates at Chokepoints, Not Mass]] — 전체를 게이트하는 건 질량(FLOPS)이 아니라 길목(가장 좁은 버스)이라는 형식 속성. - [[Impact Is Gated by Dependence]] — 한 stage의 개선은 전체가 그 stage에 의존할 때만 효과가 있다 (병목 아니면 무의미). - [[왜 고정기능 NPU + 전용 컴파일러가 범용 GPU보다 신경망 추론을 와트당 더 잘하는가 (개소리)]] — 같은 교훈(계산은 싸다, 데이터 이동이 비싸다)을 단일 칩의 와트당 효율로 가져간 버전. NPU는 DRAM 왕복을 줄이는 설계로 추론 효율을 산다.