2024년 AI의 컴퓨터 아키텍처
지난 1년 동안 하드웨어 배경을 가진 사람으로서, 저는 머신러닝 시장에서 Nvidia의 독점에 대한 많은 불만을 들었고, 이 상황을 개선하기 위한 칩을 만들 수 있는지에 대한 질문을 받았습니다. 제가 예상하는 필요 자금은 7조 달러보다는 적지만, 이번 AI 물결을 하드웨어로 가속화하는 것은 매우 어려운 문제입니다 - CNN에 집중했던 지난 물결보다 훨씬 더 어렵습니다 - 그리고 Nvidia가 경쟁자가 거의 없는 이 분야의 리더가 된 데에는 충분한 이유가 있습니다. CNN의 추론이 수학 문제였던 것과 달리, 대규모 언어 모델(LLM)의 추론은 실제로 시스템에서 최고의 성능을 얻기 위해 메모리와 I/O를 컴퓨팅과 어떻게 조율할지를 파악하는 컴퓨터 아키텍처 문제가 되었습니다.
ML 모델에서 사용되는 기본 연산
여기서 AI 시스템에 사용되는 각 연산에 대해 간략히 설명하겠지만, 각각은 구현 세부사항에 대해 훨씬 더 깊이 들어갈 수 있습니다. 컴퓨팅 시간 측면에서 가장 큰 연산은 선형 연산입니다: 행렬 곱셈과 합성곱, 그리고 어텐션 계산 내의 행렬 곱셈입니다. 누적 및 기타 비선형 함수는 시간을 훨씬 적게 소비하지만, 여러 행렬 곱셈의 계산을 결합하는 것을 방지하는 수학적 “장벽”을 만듭니다. 이러한 선형 및 비선형 연산의 조합이 통계적 관점에서 신경망에 힘을 부여합니다: 신경망의 한 층은 본질적으로 행렬 곱셈과 유사한 선형 연산과 비선형 연산의 조합입니다.
행렬 곱셈
행렬 곱셈은 신경망 계산에 사용되는 기본 연산으로, 가중치 행렬을 내부 상태의 벡터와 곱하는 것을 자주 포함합니다. 행렬-벡터 곱과 비선형 함수를 결합한 것이 완전 연결 계층의 기본 연산입니다. 행렬 곱셈은 컴퓨터 과학에서 가장 잘 연구된 알고리즘 중 하나이며, 그래픽이나 신경망용 가속기를 만드는 회사라면 누구나 효율적인 솔루션을 가지고 있을 것입니다. 행렬 곱셈을 수행하는 영리한 방법이 많이 있지만, 가장 효율적인 방법은 종종 무차별 대입입니다: 간단한 배열에 많은 승수를 사용하고 메모리 접근 방식에서 영리하게 처리하는 것입니다.
모델에 따라 이러한 행렬의 특성이 다를 수 있습니다: 이미지 기반 모델은 행렬-행렬 곱이 많고, LLM은 행렬-벡터 곱이 많습니다.
합성곱
LLM에는 없지만, 합성곱 연산은 컴퓨터 비전 모델의 일반적인 기능이며, 새로운 용도를 찾을 가능성이 있습니다. 합성곱은 작은 행렬을 훨씬 큰 행렬 위로 “슬라이딩”하고, 작은 행렬의 이동된 버전과 큰 행렬 간의 상관관계를 계산하는 것을 포함합니다. 이것은 계산 간에 많은 데이터 공유가 있는 작은 버전의 행렬-행렬 곱이 됩니다. 합성곱은 데이터 공유와 이러한 작은 행렬-행렬 곱 각각이 하나의 피연산자를 공유한다는 사실 때문에 효율적인 계산을 위한 많은 트릭이 있습니다. 행렬 곱셈을 위한 하드웨어는 합성곱과 공유하기도 비교적 쉽습니다.
비선형 연산
지수, 시그모이드, 아크탄젠트, 그리고 ReLU처럼 보이는 함수군과 같은 비선형 함수는 신경망 내부의 일반적인 연산입니다. 사용되는 수 체계의 낮은 정밀도(최대 16비트 부동소수점)를 고려할 때, 이들은 빠른 근사 또는 룩업 테이블 방법으로 계산하기가 비교적 쉽습니다. 각 신경망은 종종 비교적 적은 수의 다른 종류의 비선형 함수를 사용하므로, 이 계산을 더 빠르게 만들기 위해 매개변수를 미리 계산할 수 있습니다. 이들은 일반적으로 계산 집약적이지 않지만, 주요 기능은 각 계층의 행렬 곱셈을 기약 불가능하게 만드는 것입니다.
누적 연산
누적 연산은 벡터를 스캔하고 해당 벡터의 모든 요소에 대해 함수를 계산하는 것을 포함합니다. 신경망의 경우 가장 일반적으로 사용되는 함수는 “최대값” 함수입니다: 이것은 소프트맥스 계산과 레이어 정규화에 사용됩니다. 계산 집약적인 애플리케이션은 아니지만, 이러한 계산의 결과는 전체 벡터가 계산될 때까지 사용할 수 없습니다. 이것은 계산 장벽을 형성합니다: 이 연산의 결과에 의존하는 모든 것은 그것이 생성될 때까지 기다려야 합니다. 신경망의 계층 사이에 정규화를 두는 것이 일반적이며, 이는 숫자 크기가 통제 불능이 되는 것을 방지합니다.
어텐션
어텐션은 신경망에 사용되는 연산 측면에서 “새로운 아이”이며, 트랜스포머 모델의 정의적 특징입니다. 어텐션은 쿼리 벡터의 키-값 조회를 나타내야 하지만, 제 생각에는 실제로 일어나는 일에 대한 꽤 느슨한 직관입니다. 수행되는 연산 측면에서 어텐션은 몇 가지 행렬-벡터 곱과 소프트맥스 기반 정규화 함수를 결합한 계산입니다. 행렬-벡터 곱의 선형 특성 덕분에, 큰 행렬이 사용될 때에도 불필요한 메모리 로드를 피하기 위해 이 계산을 재배열하는 영리한 방법이 많이 있습니다: Flash Attention 논문은 가장 유명한 재배열을 다룹니다. 그러나 계산의 수치적 근사 및 큰 버전의 계산을 여러 작은 버전으로 바꾸기 위한 “랜드마크 토큰” 사용을 포함하여, 빠른 어텐션 계산을 수행하려는 다른 시도들도 있었습니다. 이러한 계산은 모두 하드웨어로 비교적 잘 변환되며, 행렬 곱셈 및 소프트맥스를 위해 구축하는 유닛 위에 구축됩니다.
컴퓨터 아키텍트가 행렬 수학 연산에 대해 생각하는 방식
산술 연산 수행은 두 가지 유형의 연산으로 나뉩니다: 칩 안팎으로 데이터를 가져오고 내보내는 것, 그리고 연산을 수행하는 것. 여기에는 “산술 강도(arithmetic intensity)”라고 하는 잘 알려진 비율이 있습니다: 수행된 메모리 연산 대 산술 연산의 비율입니다. 높은 산술 강도를 가진 문제는 컴퓨팅 제약적이며, 관련 연산을 수행할 수 있는 산술 유닛을 칩에 배치하는 능력에 의해 제한됩니다. 낮은 산술 강도를 가진 문제는 메모리 대역폭 제약적입니다: 데이터를 로드하고 저장하는 데 많은 시간을 소비할 때, 칩의 컴퓨팅 유닛은 유휴 상태입니다.
모든 칩은 사용 가능한 컴퓨팅 FLOP 및 메모리 대역폭에 따라 산술 강도를 기반으로 한 성능에 대한 “루프라인”을 가진 것으로 모델링될 수 있습니다. 이 루프라인은 특정 지점에서 무릎을 가지며, 여기서 연산이 메모리 대역폭 제약에서 컴퓨팅 제약으로 전환됩니다. 칩은 일반적으로 실행될 소프트웨어를 지원하도록 설계되며(약 5년의 지연이 있습니다 - 칩을 만드는 데 오랜 시간이 걸립니다), 이 무릎은 일반적으로 대부분의 사용자에게 비교적 좋은 지점에 선택됩니다. 그러나 이것은 모든 것에 맞는 하나의 크기입니다.
Google은 TPUv4에 대해 이 분석을 수행했고, 관심 있는 신경망이 A100 GPU의 무릎에 비교적 가까운 산술 강도를 가지고 있음을 발견했습니다. 그런데 H100은 거의 같은 곳에 무릎을 가지고 있습니다. 그러나 여기서 Google의 분석은 인용된 각 모델에서 무엇을 하고 있는지 설명하지 않으므로, 우리는 추측해야 합니다.
앞서 논의한 연산에 이것을 적용하면, 행렬 연산이 메모리 대역폭을 소비하는 것입니다. 누적 및 비선형 연산은 일반적으로 이전 계산 블록에 연결되어 산술 연산을 추가할 수 있습니다. 그러나 행렬 연산은 데이터 블록에서 작동한다는 단점이 있으며, 이는 수학 연산당 비교적 많은 양의 메모리 관리를 추가한다는 것을 의미합니다.
모델의 작업 집합
모델 계산의 규칙성 덕분에, 모델의 작업 집합은 비교적 잘 정의됩니다. 작업 집합은 다음을 포함합니다:
- 모델 가중치
- 입력 데이터
- 모델의 중간 상태
- 훈련 중에는 각 가중치에 대한 기울기(및 학습률)
이러한 항목은 모델에 따라 작거나 클 수 있습니다. LLM과 같은 모델의 경우 가중치가 거대하며, 때로는 단일 GPU에 비해 너무 커서, 입력 데이터와 중간 상태는 몇 천 개의 숫자입니다. 컴퓨터 비전 모델의 경우 가중치가 훨씬 작고 입력 데이터와 중간 상태가 훨씬 큽니다. 가중치의 크기 때문에 LLM의 훈련은 추론보다 약 3배의 메모리가 필요하지만, 그 외에는 추론과 다소 비슷해 보입니다.
그러나 작업 집합의 크기만이 중요한 것은 아닙니다. 작업 집합의 일부를 칩에(즉, 캐시에) 배치하는 능력은 모델 계산의 산술 강도에 상당한 영향을 미칩니다. 캐시와 레지스터 근처에 있는 작업 집합의 일부는 메모리에 접근하지 않고 해당 부분에 접근할 수 있게 하므로, 작업 집합의 히트 분포는 캐시에서 로드하는 대신 메모리에 접근하는 빈도를 결정합니다. 대부분의 목적에서 캐시의 대역폭은 사실상 무제한입니다.
이것은 모델이 계산되는 방식을 결정하는 모델 크기의 대략적인 분기점으로 이어집니다: GPU의 캐시(또는 ASIC의 온칩 메모리)에 맞는 작은 모델은 가중치를 통해 데이터를 스트리밍할 수 있는 반면, 더 큰 모델은 데이터를 통해 가중치를 스트리밍해야 합니다. “중간 크기” 모델이 데이터를 통해 가중치를 스트리밍하는 효과의 일부를 숨길 수 있으므로 이것은 완벽한 모델이 아니지만, 더 작은 컴퓨터 비전이나 오디오 모델과 대규모 언어 모델 간의 차이입니다. LLM은 더 클 뿐만 아니라 덜 효율적으로 계산되어야 합니다.
작은 모델: 가중치를 통한 데이터 스트리밍
2016년의 이전 AI 물결은 수천 또는 수백만 개의 매개변수를 가진 모델을 포함했습니다. 이러한 모델은 적절한 크기의 칩의 캐시 및 컴퓨팅 유닛 내부에 맞을 만큼 충분히 작습니다. 이는 추론 또는 훈련에 사용되는 칩에 전체 모델을 유지하는 것이 가능하다는 것을 의미합니다. 이 경우 캐시 계층 구조를 가진 많은 GPU 유사 시스템은 모델을 캐시에 유지하는 것이 쉬우며, 스크래치패드 또는 모델 저장 전용 특수 내부 메모리를 가진 시스템을 비용 효율적으로 구축할 수 있습니다.
이 구성은 데이터에 대해 계산하는 “자연스러운” 방법이라는 이점이 있습니다: 디스크에서 데이터를 한 번 로드하고 계산을 수행한 다음 결과를 저장하거나 사용할 수 있습니다. 또한 이 계산 방법은 추론 중에 매우 높은 산술 강도를 활용할 수 있게 합니다. 모델이 추론될 때마다 모델의 전체 실행은 입력 로드 속도에 의존합니다. 그러나 입력이 크거나 많은 전처리가 필요하거나 여러 번 스캔해야 하는 경우, 특히 캐싱에 의존하는 시스템의 경우 산술 강도가 감소할 수 있습니다: 입력을 처리하면 캐시에서 모델이 제거되어 추가 로드가 발생할 수 있습니다.
큰 모델: 데이터를 통한 가중치 스트리밍
수십억 개의 매개변수를 가진 더 큰 모델의 경우, 더 이상 단일 칩에 가중치를 유지할 수 없습니다(참고: HBM 메모리는 “칩 위”로 간주되지 않습니다). 그러나 선형 대수의 사용은 여전히 활용할 수 있는 규칙적인 계산 패턴이 있다는 것을 의미합니다: 모든 모델은 계층별로 진행됩니다. 대형 모델의 중간 상태는 여전히 비교적 작습니다: 원래 Llama 모델 중 가장 큰 것은 단지 8k 부동소수점 숫자의 내부 상태와 4k 입력 토큰의 컨텍스트 창을 사용합니다.
그러나 모델의 중간 곱만 저장하면 작업 집합은 여전히 비교적 작습니다. 이러한 모델을 계산하기 위해 일반적인 접근 방식은 데이터를 통해 모델을 스트리밍하는 것입니다. 한 배치의 데이터가 칩에 로드되고, 추론이 계층별로 진행됨에 따라 모델의 내부 상태가 계속 칩에 남아 있습니다.
이 구성에서 칩 외부에 저장된 데이터는 반복적으로 접근되므로, 디스크(또는 CPU의 RAM이나 다른 원격 사이트)에서 모델 가중치를 로드하는 것은 일반적으로 너무 느린 것으로 간주됩니다. 이것이 많은 LLM이 GPU에 대해 높은 VRAM 요구 사항을 가진 이유입니다: 전체 모델(추가로 추론 속도를 높이는 데 사용되는 K-V 캐시와 같은 몇 가지 다른 것들)을 GPU의 VRAM 내부에 저장해야 합니다.
가중치가 칩으로 스트리밍된다는 사실 때문에, 이 계산 모델의 산술 강도는 비교적 매우 낮으며, 로드될 때 가중치를 재사용하는 능력에 의해 주도됩니다. 많은 모델에는 재사용을 활용할 수 있는 연산이 있지만, LLM은 행렬-벡터 곱의 보급 덕분에 매우 제한된 재사용을 가지고 있습니다. GPU의 GPT-2는 특히 로드된 바이트당 약 2개의 연산의 산술 강도를 가지고 있습니다. 그러나 가중치 재사용 덕분에 BERT 모델은 바이트당 약 100-200개의 연산까지 올라갑니다.
소프트웨어 수준의 트릭: 배칭, 양자화 및 희소성
거의 모든 컴퓨팅 애플리케이션, 특히 머신러닝 모델의 컴퓨팅 강도를 배칭으로 증가시킬 수 있습니다. 배칭은 LLM과의 여러 채팅이나 컴퓨터 비전 모델과의 여러 이미지와 같이 동일한 유형의 여러 병렬 계산을 동시에 수행하는 것을 의미합니다. 그러나 배칭은 병렬 작업을 사용할 수 있어야 합니다: 로컬 LLM은 수천 또는 수백만 명의 동시 사용자를 가진 OpenAI의 중앙 집중식 챗봇 시스템만큼 배칭의 이점을 얻지 못할 것입니다. 더 큰 배치 크기를 위해 구축된 시스템은 일반적으로 더 높은 컴퓨팅 대 메모리 비율을 사용할 수 있습니다.
양자화는 더 작은 숫자를 사용하여 작업 집합의 크기를 줄이는 것을 포함합니다. 이것은 추론에 매우 효과적인 전략입니다. 완전히 사전 훈련된 모델을 가지고 있고 이제 가중치의 정밀도를 줄이는 최선의 방법을 찾기 위해 많은 수학을 할 수 있어서, 모델 정확도의 비교적 낮은 손실로 가중치당 실질적으로 2-4비트를 저장합니다. 그러나 양자화된 훈련은 다소 어렵습니다: 가중치를 미리 알 수 없으므로 양자화된 추론의 일반적인 수학적 트릭이 작동하지 않습니다. 많은 훈련 프로세스는 오늘날 8비트 부동소수점 숫자가 필요하며, 이것은 이미 16비트 부동소수점이 훈련의 표준이었던 2020년에 비해 “양자화된” 것으로 간주됩니다. 양자화는 양자화된 형태에서 계산에 사용할 수 있는 형태로 변환하기 위한 몇 가지 추가 연산을 추가하지만(모델의 내부 상태는 여전히 FP8 또는 FP16입니다), 그러한 연산은 제한 요인이 아닙니다.
희소성은 일종의 양자화로 생각할 수 있습니다. 모델 희소화는 신경망의 결과에 중요하지 않은 가중치를 찾아서 해당 가중치를 0과 동일한 것으로 처리하여 계산에서 제외하는 것을 포함합니다. 이것은 작업 집합 크기와 출력을 계산하는 데 필요한 산술 연산 수를 모두 줄입니다. 희소성은 전체 가중치 블록을 0으로 처리할 수 있을 때 더 효과적이지만, 행렬 곱셈 유닛이 이를 위해 설계된 경우 세밀한 수준에서도 적용할 수 있습니다: Nvidia의 것이 그렇습니다. 그러나 희소성은 훈련에서 활용하는 것이 기본적으로 불가능합니다: 모델을 훈련하기 전에 어떤 가중치가 중요할지 알 수 없습니다.
이 모든 것은 모델 추론의 정확한 작동 지점을 조정할 수 있는 노브이며, 시스템을 구축하는 사람들이 하드웨어에서 최대한을 끌어낼 수 있도록 도와줍니다.
“7조 달러” 질문
지난 AI 가속기 물결은 “작은 모델” 케이스에 집중하는 이점을 얻었습니다. 이것은 회로 설계자의 꿈입니다: 계산해야 하는 상태가 일반적으로 칩에 완전히 맞을 수 있으므로, 효율적으로 계산할 수 있는 ASIC을 설계하는 것이 여러분의 통제 내에 있습니다. 실리콘의 경계 밖에서 무슨 일이 일어나는지에 대해 그다지 걱정하지 않고 이러한 시스템의 “새로운” 수학을 구현하는 새로운 회로와 시스템을 자유롭게 설계하여 성능을 향상시킬 수 있습니다. 이것이 우리가 이 시기에 아날로그 행렬 곱셈에서 흥미로운 새로운 종류의 산술, 단일 칩 대규모 SIMT 시스템에 이르기까지 많은 새롭고 흥미로운 새 아키텍처를 본 이유의 일부입니다. 이들은 에너지 효율성과 속도에서 엄청난 이득을 약속했고 그러한 이득을 달성했지만, 루프라인 모델의 무릎을 오른쪽으로 밀어내는 자신을 발견했습니다: 많은 경우 메모리 연산을 완전히 피할 수 있을 때만 그 효율성을 얻을 수 있습니다.
내일의 AI 가속기는 데이터를 통한 가중치 스트리밍의 경우에 대처하기 위해 메모리에 집중해야 합니다. 이러한 칩의 주요 목표는 “짐승을 먹이는” 방법을 찾고 작업 집합을 컴퓨팅에 더 가깝게 가져오는 것입니다. 이것이 Nvidia가 가능한 한 최고의 범용 병렬 컴퓨팅 엔진을 설계하여 잘 수행하는 방법을 파악한 것입니다. 그들은 데이터를 컴퓨팅에 가깝게 가져오는 것이 HPC 시스템의 일반적인 문제라는 이점을 가지고 있었으며, 이러한 시스템은 작업 집합이 크지만 LLM보다 산술 강도가 높은 경향이 있습니다. 다른 모든 사람들이 지금 이것을 하기 위해 서두르는 동안, Nvidia는 그들의 Tesla 가속기가 HPC 세계를 장악하면서 거의 10년 동안 이 문제에 대해 작업해 왔습니다.
Groq 및 Cerebras와 같은 일부 스타트업은 비교적 틈새 방식이긴 하지만, 이 새로운 환경에 다소 적응할 수 있음을 발견했습니다. 이 영역의 미래 기술은 새로운 종류의 인-메모리 컴퓨팅, 컴퓨팅에 메모리를 연결하는 새로운 방법, 또는 훨씬 더 많은 RAM과 컴퓨팅을 서로 가까이 얻을 수 있게 하는 새로운 멀티 칩 네트워크일 수 있습니다. 작업 집합을 컴퓨팅에 더 가깝게 가져오는 것은 작업 집합을 압축하는 것을 의미할 수도 있으며, 이는 일반적으로 양자화를 통해 발생합니다. 그러나 미래의 가속기는 접근 패턴이 매우 예측 가능한 작업 집합을 압축하는 새로운 방법을 찾을 수 있으며, 이는 LLM 가중치에 전통적인 압축 방법을 사용하는 것과 같이(gzip 스타일 압축이 잘 작동하지 않는 이유가 많지만) 반드시 동일한 작업 집합 제한을 받지 않을 수 있습니다. 블록 부동소수점은 여기서 하나의 쉬운 옵션이며, 많은 숫자 사이에서 하나의 지수를 공유하여 부동소수점 숫자를 압축하지만, 아마도 더 영리한 일이 있을 것입니다.
로컬 LLM(해당 시장이 존재하는 경우)에서 충족되지 않은 틈새가 있는 것으로 보입니다: 큰 메모리 용량과 높은 메모리 대역폭을 비교적 낮은 성능의 컴퓨팅 코어와 결합한 것은 아직 아무도 하지 않는 것 같습니다. 그러나 이것은 메모리와 컴퓨팅 사이의 인터페이스에 존재하는 문제입니다. 반대로, 아마도 작업을 사용할 수 있는 Google 및 OpenAI와 같은 회사를 위한 초대형 배치 크기에서 또 다른 기회가 있을 수 있으며, 그 경우에 사용할 솔루션은 낮은 배치 크기에 대한 솔루션과 매우 다르게 보일 것입니다. 이러한 시스템은 오늘날 우리가 가진 것보다 맞춤형 슈퍼컴퓨터처럼 보일 것입니다.
결론
LLM을 몇 배 더 빠르게 만드는 데 필요한 발전은 회로 수준보다는 시스템 수준일 가능성이 높지만, 대부분의 “AI”에 대해 오늘날 존재하는 GPU 기술이 반드시 빗나간 것은 아닙니다. Nvidia/AMD의 제조 공정 우위는 추론에 사용되는 계산에만 집중하여 얻을 수 있는 효율성을 충분히 보상할 수 있습니다. 이미 칩을 회전시키는 데 필요한 1천만-2천만 달러를 모으는 데 어려움을 겪고 있는 스타트업의 경우, 이러한 완전히 새로운 시스템을 설계하는 데 필요할 수 있는 수억 달러를 모으는 것은 손이 닿지 않을 수 있습니다. 모델 아키텍처의 발전 속도를 고려할 때, 이 연구가 도착할 때까지 관련성이 있을 것이라는 보장도 없습니다: 2030년의 모델은 오늘날의 모델이 2016년의 모델과 다른 것만큼이나 오늘날의 모델과 다를 수 있습니다. 특히 모델에 대한 새로운 아키텍처가 나타남에 따라, 매우 낮은 산술 강도의 추세는 계속되지 않을 수 있습니다. 그럼에도 불구하고 저는 이 분야의 스타트업이 무엇을 만들어낼 수 있을지 기대하고 있습니다.
좋은 질문들이네요! 실무 엔지니어링 관점에서 하나씩 설명해드리겠습니다.
1. 행렬 곱셈에서 브루트 포스가 효율적인 이유
정확히 맞습니다! 이론적 알고리즘 복잡도(O(n³) vs O(n^2.8))보다 하드웨어 현실이 더 중요합니다.
실무에서 일어나는 일:
- Strassen 알고리즘 같은 “똑똑한” 방법은 곱셈 횟수를 줄이지만, 메모리 접근 패턴이 불규칙해짐
- 현대 칩에서는 메모리 접근 비용 » 곱셈 비용
- GPU/TPU는 수천 개의 곱셈기를 병렬로 돌릴 수 있지만, 메모리 대역폭은 제한적
브루트 포스의 장점:
- 메모리 접근이 예측 가능 → 프리페칭 가능
- 행렬을 타일(tile)로 나눠서 캐시 친화적으로 처리
- SIMD/벡터 명령어와 완벽하게 호환
- 예: 8×8 행렬 블록을 레지스터에 로드해서 64개 곱셈을 한 번에 처리
실제로 NVIDIA cuBLAS나 Intel MKL은 O(n³) 알고리즘을 사용하지만, 메모리 계층 구조를 극한까지 최적화합니다.
2. 행렬-벡터 vs 행렬-행렬 곱셈의 차이
핵심 차이는 “재사용”입니다:
행렬-행렬 곱 (A × B = C)
A (1000×1000) × B (1000×1000) = C (1000×1000)
- A의 각 행을 1000번 재사용 (B의 모든 열에 대해)
- B의 각 열을 1000번 재사용 (A의 모든 행에 대해)
- 메모리 로드: 2M 개 숫자
- 연산: 1B 개 곱셈
- 산술 강도: 500 ops/byte (매우 높음)
행렬-벡터 곱 (A × v = w)
A (1000×1000) × v (1000×1) = w (1000×1)
- A의 각 요소를 단 1번만 사용
- v의 각 요소를 1000번 재사용 (작은 벡터라 캐시에 계속 있음)
- 메모리 로드: ~1M 개 숫자
- 연산: 1M 개 곱셈
- 산술 강도: 1-2 ops/byte (매우 낮음)
LLM에서 문제가 되는 이유:
- GPT 추론: 배치 크기 1일 때 거의 모든 연산이 행렬-벡터 곱
- 70B 모델 → 280GB의 가중치를 로드해야 하는데, 각 숫자를 단 1번만 사용
- GPU 컴퓨팅 유닛은 90% 이상 놀고 있음 (메모리 대기 중)
3. 비선형 연산이 “기약 불가능하게” 만드는 이유
이건 선형대수의 핵심입니다.
만약 비선형 연산이 없다면:
# Layer 1
y₁ = W₁ × x
# Layer 2
y₂ = W₂ × y₁
# Layer 3
y₃ = W₃ × y₂
이것은 결국:
y₃ = W₃ × (W₂ × (W₁ × x))
= (W₃ × W₂ × W₁) × x
= W_combined × x # 한 번의 행렬 곱으로 축약됨!
이렇게 되면:
- 100개 레이어 신경망 = 1개 레이어 신경망
- 아무리 깊어도 표현력은 선형 함수 하나와 동일
- 신경망의 의미가 없음
비선형 연산이 있으면:
y₁ = ReLU(W₁ × x)
y₂ = ReLU(W₂ × y₁)
y₃ = ReLU(W₃ × y₂)
이제는:
- ReLU(W₂ × ReLU(W₁ × x)) ≠ (W₂ × W₁) × x
- 레이어를 합칠 수 없음 = “기약 불가능”
- 각 레이어를 순차적으로 계산해야 함
하드웨어 관점에서:
- 레이어를 합칠 수 없다 = 메모리에 중간 결과를 쓰고 다시 읽어야 함
- 계산 최적화의 여지가 줄어듦
- 비선형 연산이 “계산 장벽”을 만듦
4. 루프라인 모델의 “무릎(knee)”
이건 실제로 널리 쓰이는 컴퓨터 아키텍처 용어입니다!
루프라인 모델 시각화:
성능
(GFLOPS)
│
1000│ ┌─────────────── (컴퓨팅 제한 영역)
│ /
500│ / ← 무릎 (knee)
│ /
100│─────/ (메모리 대역폭 제한 영역)
│
└────────────────────────> 산술 강도 (ops/byte)
10 50 100
무릎이 의미하는 것:
- 왼쪽 (낮은 산술 강도): 메모리가 느려서 성능이 제한됨
- 아무리 빠른 GPU여도 데이터 로드를 기다려야 함
- 오른쪽 (높은 산술 강도): 컴퓨팅 유닛이 부족해서 제한됨
- 메모리는 충분히 빠르지만 계산이 따라가지 못함
- 무릎 지점: 메모리와 컴퓨팅이 균형을 이루는 지점
- 예: A100의 경우 약 47 ops/byte
왜 “무릎(knee)”인가?
- 그래프가 꺾이는 지점이 무릎처럼 생김
- 두 제한 요소가 만나는 전환점
- 영어권에서 보편적으로 사용하는 기술 용어
5. 데이터/가중치 스트리밍 패턴
이게 가장 중요한 개념인데, 예시로 설명하겠습니다.
작은 모델: “가중치를 통해 데이터 스트리밍”
┌──────────────────┐
│ GPU │
│ │
│ ┌────────────┐ │
│ │ Weights │ │ ← 모델 전체가 칩 안에 상주
│ │ (10MB) │ │ (캐시나 SRAM에)
│ └────────────┘ │
│ │
└──────────────────┘
↑
│ 데이터 흐름
│
[이미지1] → [이미지2] → [이미지3] ...
작동 방식:
- 모델 가중치를 칩에 한 번 로드
- 이미지들을 순차적으로 흘려보냄
- 각 이미지마다 같은 가중치를 재사용
예시: ResNet-50 (25MB 모델)
- GPU에 모델 전체 로드
- 1000장의 이미지를 배치로 처리
- 가중치는 1000번 재사용
큰 모델: “데이터를 통해 가중치 스트리밍”
┌────────────────────┐
│ GPU │
│ │
│ ┌──────────────┐ │
│ │ Halfway State│ │ ← 작은 데이터만 칩에 유지
│ │ (8KB) │ │ (토큰 임베딩 등)
│ └──────────────┘ │
│ │
└────────────────────┘
↑
│ 가중치 흐름
│
[Layer1] → [Layer2] → [Layer3] ...
(2GB) (2GB) (2GB)
작동 방식:
- 입력 데이터(프롬프트)를 칩에 로드
- Layer 1 가중치를 메모리에서 스트리밍
- 계산 → 중간 결과 업데이트
- Layer 2 가중치를 스트리밍…
- 모든 레이어를 순차 처리
예시: Llama-70B
- 모델: 280GB (칩에 못 들어감)
- 입력: 2048 토큰 × 8KB = 16MB (칩에 들어감)
- 레이어별로 가중치를 HBM에서 계속 로드
비유로 설명하면:
작은 모델 (가중치 통해 데이터 스트리밍):
- 공장에 기계(가중치)를 설치해두고
- 원자재(데이터)를 컨베이어 벨트로 흘려보냄
- 기계는 고정, 재료만 교체
큰 모델 (데이터 통해 가중치 스트리밍):
- 하나의 원자재(데이터)를 작업대에 고정
- 다양한 공구(가중치)를 하나씩 가져와서 작업
- 재료는 고정, 공구만 교체
실무적 영향:
| 작은 모델 | 큰 모델 (LLM) | |
|---|---|---|
| 메모리 사용 | 모델 + 배치 데이터 | 모델 (압도적) |
| 병목 | 데이터 로딩/전처리 | 가중치 메모리 대역폭 |
| 배칭 효과 | 매우 큼 | 제한적 |
| 최적화 포인트 | 데이터 파이프라인 | HBM 대역폭, 양자화 |
이래서 LLM 추론에는 A100 같은 HBM 대역폭이 높은 GPU가 필요하고, 작은 비전 모델은 저렴한 GPU로도 빠르게 돌릴 수 있는 겁니다!
완벽한 질문들입니다! 핵심을 정확히 파악하고 계시네요. 하나씩 명확히 설명하겠습니다.
6. HBM과 “칩 위(on-chip)” 메모리의 차이
HBM = GPU VRAM 맞습니다! 하지만 물리적 위치가 중요합니다.
메모리 계층 구조:
┌─────────────────────────────────────┐
│ GPU │
│ │
│ ┌──────────┐ ┌─────────────┐ │
│ │ Register │ │ L1 Cache │ │ ← "칩 위(on-chip)"
│ │ (KB) │ │ (100s KB) │ │ 매우 빠름 (1 cycle)
│ └──────────┘ └─────────────┘ │
│ │
│ ┌─────────────────────────────┐ │
│ │ L2 Cache / SRAM │ │ ← 여전히 "칩 위"
│ │ (10s MB) │ │ 빠름 (수십 cycles)
│ └─────────────────────────────┘ │
│ │
└─────────────────────────────────────┘
↕ (칩 경계)
══════════════════════════════ ← PCB 기판
↕
┌─────────────────────────────────────┐
│ HBM Stack (Individual Chips) │
│ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │ ← "칩 외부(off-chip)"
│ │HBM1│ │HBM2│ │HBM3│ │HBM4│ │ 하지만 매우 가까움
│ │16GB│ │16GB│ │16GB│ │16GB│ │ 느림 (수백 cycles)
│ └────┘ └────┘ └────┘ └────┘ │
└─────────────────────────────────────┘
핵심 차이점:
| On-Chip (SRAM/캐시) | Off-Chip (HBM/VRAM) | |
|---|---|---|
| 위치 | GPU 다이 내부 | 별도의 메모리 칩 |
| 용량 | A100: 40MB L2 | A100: 40-80GB HBM |
| 대역폭 | ~20 TB/s | ~2 TB/s |
| 지연시간 | 수 사이클 | 수백 사이클 |
| 전력 | 적음 | 많음 |
왜 이게 중요한가?
“칩에 올린다” = SRAM/캐시에 올린다:
- 10배 이상 빠른 접근
- 전력 효율 훨씬 좋음
- 하지만 용량이 제한적
작은 모델의 꿈:
# 25MB ResNet-50
전체 모델이 L2 캐시(40MB)에 들어감
→ HBM 접근 최소화
→ 미친 속도
큰 모델의 현실:
# 280GB Llama-70B
칩 내부: 40MB (0.014%)
HBM에 저장: 280GB (99.986%)
→ 계속 HBM ↔ 칩 간 데이터 이동
→ 대역폭이 병목
7. 레이어별 실행 메커니즘
내가 제대로 이해한 게 맞으면, LLM같은 큰 모델은 단일 칩에 모델을 올릴 수 없지만, 대신 모델(즉, 가중치)이 여전히 비선형 연산으로 쪼개지는 레이어를 갖고 이 레이어 하나를 계산하는 일은 단일 칩 위에서 가능한 수준이기 때문에, 입력(프롬프트)를 칩에 올린 다음 모델을 레이어 바이 레이어로 실행해서 효율적인 계산을 달성한다는 걸까?
정확히 맞습니다! 조금 더 상세히 설명하면:
LLM 추론의 실제 실행:
# Llama-70B는 80개 레이어
# 각 레이어는 ~3.5GB
입력 = "What is AI?" (토큰 임베딩: 약 16MB)
for layer in range(80):
# 1. 이 레이어의 가중치를 HBM → 칩으로 로드
W_query = load_from_HBM(3.5GB) # 시간 많이 걸림
# 2. 칩 위에서 계산 (빠름)
hidden_state = compute_attention(hidden_state, W_query)
# 3. 다음 레이어를 위해 중간 상태만 유지
# W_query는 버림 (다시 안 씀)
왜 이게 가능한가?
각 레이어 계산에 필요한 것:
- 가중치: 3.5GB (HBM에서 로드)
- 중간 상태: 16MB (칩에 계속 유지)
- 임시 버퍼: 수백 MB (계산 중)
A100의 온칩 리소스:
- L2 캐시: 40MB
- 레지스터 파일: ~20MB
- → 중간 상태 + 계산을 충분히 칩 위에서 처리 가능!
실제 타이밍:
Layer 1: [HBM 로드 500ms] [계산 50ms]
Layer 2: [HBM 로드 500ms] [계산 50ms]
Layer 3: [HBM 로드 500ms] [계산 50ms]
...
Layer 80: [HBM 로드 500ms] [계산 50ms]
총 시간: 44초 (80 × 550ms)
실제 계산: 4초 (80 × 50ms) = 9%만 계산!
이래서 메모리 대역폭이 병목입니다!
8. 배치 처리와 GPU 활용도
7에 이어서, 저렇게 각 입력(프롬프트)마다 모델의 각 레이어를 계산하게 되면 실제로 칩에 올라가는 병렬 계산이 GPU가 병렬로 처리 가능한 계산 능력보다 현저하게 적기 때문에, 더 많은 처리량을 위해서 다른 입력에 대한 처리를 배치 처리 해서 서비스를 효율적으로 한다는 걸까?
완벽한 이해입니다! 수치로 보여드리겠습니다.
배치 크기 1 (단일 프롬프트):
A100 GPU 스펙:
- 연산 능력: 312 TFLOPS (FP16)
- HBM 대역폭: 2 TB/s
Llama-70B Layer 1 계산:
- 가중치 로드: 3.5GB ÷ 2TB/s = 1.75ms
- 행렬-벡터 곱: 3.5B × 8K = 28T ops
- 계산 시간: 28T ÷ 312T = 0.09ms
실제 활용도: 0.09 ÷ 1.75 = 5%
→ GPU의 95%가 놀고 있음!
배치 크기 32 (32개 프롬프트 동시):
같은 Layer 1:
- 가중치 로드: 3.5GB ÷ 2TB/s = 1.75ms (동일!)
- 행렬-행렬 곱: 3.5B × 8K × 32 = 896T ops
- 계산 시간: 896T ÷ 312T = 2.87ms
실제 활용도: 2.87 ÷ (1.75 + 2.87) = 62%
→ 훨씬 나아짐!
OpenAI/Google이 하는 일:
# Continuous batching (vLLM 같은 시스템)
요청 큐 = [
"Tell me a joke", # 생성 중: 토큰 5/100
"Explain quantum physics", # 생성 중: 토큰 50/200
"Write a poem", # 생성 중: 토큰 80/150
... (수백 개)
]
# 각 forward pass마다:
# 1. 모든 요청의 다음 토큰을 동시에 계산
# 2. 완료된 요청 제거
# 3. 새 요청 추가
# → GPU 활용도 80%+
왜 로컬 LLM은 이걸 못하나?
- 개인 사용자: 보통 한 번에 1개 프롬프트
- 배치 크기 1 → GPU 5% 활용
- 같은 하드웨어인데 OpenAI보다 15배 느림!
9. 산술 강도와 배치 처리
산술 강도가 산술연산/메모리연산 이면 산술연산이 많을수록 강도가 높고 메모리연산이 많을수록 강도가 낮다는 뜻인데, 그래서 큰 모델의 경우 각 입력에 대한 처리(추론)에서 메모리 연산이 겁나게 많아서 산술 강도가 현저하게 낮다는건가? 그래서 가중치를 재사용하는 능력(즉, 배치처리)가 중요해진거고?
정확합니다! 수식으로 증명하겠습니다.
산술 강도 공식:
산술 강도 = 수행한 연산 수 (FLOP) / 메모리 이동량 (Byte)
배치 크기별 비교:
배치 크기 1 (행렬-벡터):
Matrix: [70B × 8K] @ Vector: [8K × 1]
메모리 로드:
- 행렬: 70B × 2 bytes = 140GB
- 벡터: 8K × 2 bytes = 16KB
- 총: ~140GB
연산:
- 곱셈: 70B × 8K = 560G FLOP
산술 강도: 560G / 140G = 4 ops/byte
배치 크기 128 (행렬-행렬):
Matrix: [70B × 8K] @ Matrix: [8K × 128]
메모리 로드:
- 행렬: 140GB (동일!)
- 입력: 8K × 128 × 2 = 2MB
- 총: ~140GB (거의 동일)
연산:
- 곱셈: 70B × 8K × 128 = 71.7T FLOP
산술 강도: 71.7T / 140G = 512 ops/byte
128배 개선!
루프라인 모델에 표시하면:
성능
│
312T│ ┌─────────────
│ /│
20T│ / │← 배치 128 (512 ops/byte)
│ / │ 컴퓨팅 제한 영역
1T│─────/ │← 배치 1 (4 ops/byte)
│ │ 메모리 제한 영역
└─────────────────────────> 산술 강도
47 (A100의 knee)
실무 데이터:
| 배치 크기 | 산술 강도 | GPU 활용도 | 처리량 |
|---|---|---|---|
| 1 | 4 ops/byte | 5% | 1x |
| 8 | 32 ops/byte | 30% | 7x |
| 32 | 128 ops/byte | 65% | 25x |
| 128 | 512 ops/byte | 85% | 90x |
10. 추가 인사이트
왜 훈련은 추론보다 효율적인가?
# 추론 (배치 1)
output = forward(input, weights)
# 산술 강도: 4 ops/byte
# 훈련 (배치 32)
output = forward(batch_input, weights) # 128 ops/byte
gradients = backward(output, weights) # 128 ops/byte
weights = update(weights, gradients) # 추가 연산
# 같은 가중치를 3번 재사용!
양자화가 중요한 이유:
FP16 Llama-70B: 140GB
- HBM 로드: 140GB ÷ 2TB/s = 70ms
INT4 Llama-70B: 35GB (4배 압축)
- HBM 로드: 35GB ÷ 2TB/s = 17.5ms
- 4배 빠른 추론!
하지만 산술 강도는 여전히 낮음 (4 ops/byte)
→ 여전히 메모리 제약적
Groq가 다른 이유:
Groq의 LPU는:
- 엄청난 온칩 SRAM (230MB)
- 낮은 정밀도 (INT8)
- 매우 높은 내부 대역폭
→ 작은 모델을 칩에 올려서 데이터 스트리밍 패턴으로 실행 → 70B 모델은 못 돌리지만, 7B~13B 모델은 매우 빠름
결론: 당신의 이해가 정확합니다! LLM 추론은 본질적으로 “메모리 대역폭 문제”이고, 배치 처리는 이를 완화하는 핵심 전략입니다. 이래서 Nvidia가 H100에서 HBM3 (3TB/s)로 업그레이드하고, 삼성/SK하ynix가 HBM4 개발에 수조 원을 투자하는 겁니다!