Conversational AI / NLP

LLM 기술 마스터하기: 인퍼런스 최적화

Reading Time: 15 minutes

트랜스포머 레이어를 쌓아 대규모 모델을 만들면 다양한 언어 작업에서 정확도가 향상되고, 퓨샷 러닝이 가능하며, 심지어 인간에 가까운 능력을 발휘할 수 있습니다. 이러한 파운데이션 모델은 트레이닝 비용이 많이 들며 추론 과정에서 메모리와 컴퓨팅 집약적일 수 있습니다 (주로 반복되는 비용임). 오늘날 가장 널리 사용되는 거대 언어 모델(LLM)은 그 크기가 수백억에서 수천억 개의 매개변수에 달할 수 있으며, 사용 사례에 따라 긴 입력(또는 컨텍스트)을 수집해야 할 수도 있어 비용이 추가될 수 있습니다.

이번 블로그에서는 LLM 추론에서 가장 시급한 과제와 몇 가지 실용적인 해결책에 대해 논의합니다. 독자는 트랜스포머 아키텍처와 어텐션 메커니즘 전반에 대한 기본적인 이해가 있어야 합니다. 다음 섹션에서 다룰 예정인 LLM 추론의 복잡성에 대한 이해는 필수적입니다.

LLM 추론 이해하기

널리 사용되는 대부분의 디코더 전용 LLM (예: GPT-3)은 기본적으로 다음 단어의 예측자로 사용되는 인과 관계 모델링 목표에 기반하여 사전 학습됩니다. 이러한 LLM은 토큰 시리즈를 입력으로 받아 중단 기준(예: 생성할 토큰 수 제한 또는 중단 단어 목록)을 충족할 때까지 또는 생성 종료를 나타내는 특수 <end> 토큰을 생성할 때까지 후속 토큰을 자동 회귀적으로 생성합니다. 이 프로세스에는 프리필(prefill) 단계와 디코드 단계의 두 단계가 포함됩니다.

토큰은 모델이 처리하는 언어의 원자적인(atomic) 부분이라는 점을 기억하세요. 하나의 토큰은 약 4개의 영어 문자에 해당합니다. 자연어로 된 모든 입력은 모델에 입력하기 전에 토큰으로 변환됩니다. 

프리필 단계 또는 입력 처리

프리필 단계에서 LLM은 입력 토큰을 처리하여 중간 상태(키와 밸류)를 연산하고, 이 중간 상태는 “첫 번째” 새 토큰을 생성하는 데 사용됩니다. 각각의 새 토큰은 이전의 모든 토큰에 의존하지만, 입력의 전체 범위를 알 수 있기 때문에 높은 수준에서 이는 고도로 병렬화된 매트릭스-매트릭스 연산입니다. 이는 GPU 사용률을 효과적으로 포화시킵니다.

디코드 단계 또는 출력 생성

디코드 단계에서 LLM은 중단 기준이 충족될 때까지 출력 토큰을 한 번에 하나씩 자동 회귀적으로 생성합니다. 각 순차 출력 토큰은 이전 반복의 모든 출력 상태(키와 값)를 알고 있어야 합니다. 이는 행렬-벡터 연산과 같아서 프리필 단계에 비해 GPU 연산 능력을 제대로 활용하지 못합니다. 데이터(가중치, 키, 값, 활성화)가 메모리에서 GPU로 전송되는 속도가 지연 시간을 결정하며, 계산이 실제로 얼마나 빨리 이루어지는지가 지연 시간을 결정하지 않습니다. 다시말하면, 이 작업은 메모리 바운드 연산입니다.

이번 블로그에 소개된 많은 추론 과제와 해당 솔루션은 효율적인 어텐션 모듈, 키와 밸류의 효과적인 관리 등등 이번 디코드 단계의 최적화와 관련이 있습니다.

LLM마다 서로 다른 토크나이저를 사용할 수 있으며, 따라서 이들 간의 출력 토큰을 비교하는 것이 간단하지 않을 수 있습니다. 추론 처리량을 비교할 때, 두 LLM의 초당 출력 토큰이 비슷하더라도 서로 다른 토크나이저를 사용하는 경우 동등하지 않을 수 있습니다. 이는 해당 토큰이 다른 수의 문자를 나타낼 수 있기 때문입니다.

배칭

GPU 사용률과 처리량을 효과적으로 개선하는 가장 간단한 방법은 배칭를 사용하는 것입니다. 여러 요청이 동일한 모델을 사용하기 때문에 가중치에 대한 메모리 비용이 분산됩니다. 더 큰 배치가 GPU로 전송되어 한 번에 처리되면 사용 가능한 연산 능력을 더 많이 활용할 수 있습니다. 

그러나 배치 크기는 특정 한도까지만 늘릴 수 있으며, 어떤 지점에서는 메모리 오버플로우가 발생할 수 있습니다. 이러한 현상이 발생하는 이유를 더 잘 이해하려면 키-밸류(KV) 캐싱과 LLM 메모리 요구 사항을 살펴봐야 합니다.

기존의 배칭(스태틱 배칭이라고도 함)는 차선책입니다. 배치의 각 요청에 대해 LLM이 서로 다른 수의 완료 토큰을 생성할 수 있고, 따라서 실행 시간도 달라지기 때문입니다. 결과적으로 배치의 모든 요청은 가장 긴 요청이 완료될 때까지 기다려야 하며, 이는 생성 길이의 큰 차이로 인해 악화될 수 있습니다. 이 문제를 완화할 수 있는 방법에는 인-플라이트 배칭과 같은 방법이 있으며, 이에 대해서는 나중에 설명하겠습니다.

키-밸류 캐싱

디코드 단계에 대한 일반적인 최적화 중 하나는 KV 캐싱입니다. 디코드 단계에서는 각 타임 스텝에서 단일 토큰을 생성하지만 각 토큰은 모든 이전 토큰의 키 및 밸류 텐서(미리 채울 때 계산된 입력 토큰의 KV 텐서 및 현재 타임 스텝까지 계산된 모든 새 KV 텐서 포함)에 따라 달라집니다. 

각 타임 스텝에서 모든 토큰에 대해 이러한 텐서를 모두 다시 계산하지 않으려면 GPU 메모리에 캐시할 수 있습니다. 매 반복마다 새로운 요소가 계산되면 실행 중인 캐시에 추가하여 다음 반복에 사용할 수 있습니다. 일부 구현에서는 모델의 각 레이어에 대해 하나의 KV 캐시가 있습니다.

그림 1. 키-밸류 캐싱 메커니즘의 설명

LLM 메모리 요구사항 

사실상 GPU LLM 메모리 요구사항에 영향을 미치는 두 가지 주요 요인은 모델 가중치와 KV 캐시입니다.

  • 모델 가중치: 모델 파라미터는 메모리를 차지합니다. 예를 들어, 70억 개의 파라미터가 있는 모델(예: Llama 2 7B)을 16비트 정밀도(FP16 또는 BF16)로 로드할 경우 약 7B * sizeof(FP16) ~= 14GB의 메모리가 사용됩니다.
  • KV 캐싱: 중복 계산을 피하기 위해 셀프-어텐션 텐서 캐싱이 메모리를 차지합니다.

배칭을 사용하면 배치에 포함된 각 요청의 KV 캐시는 여전히 개별적으로 할당되어야 하며 메모리 사용량이 커질 수 있습니다. 아래 공식은 오늘날 대부분의 일반적인 LLM 아키텍처에 적용되는 KV 캐시의 크기를 나타냅니다.

Size of KV cache per token in bytes = 2 * (num_layers) * (num_heads * dim_head) *  precision_in_bytes

2의 첫 번째 계수는 K 행렬과 V 행렬을 설명합니다. 일반적으로 (num_heads * dim_head)의 값은 트랜스포머의 hidden_size (또는 모델의 차원, d_model)와 동일합니다. 이러한 모델 속성은 일반적으로 모델 카드 또는 관련 구성 파일에서 찾을 수 있습니다.

이 메모리 크기는 입력 시퀀스의 각 토큰에 대해 입력 배치 전체에 걸쳐 필요합니다. 반정밀도(Half-precision)라고 가정할 때, KV 캐시의 총 크기는 아래 공식에 의해 계산됩니다.

Total size of KV cache in bytes = (batch_size) * (sequence_length) * 2 * (num_layers) * (hidden_size) *  sizeof(FP16)

예를 들어 16비트 정밀도, 배치 크기가 1인 Llama 2 7B 모델의 경우 KV 캐시 크기는 1 * 4096 * 2 * 32 * 4096 * 2바이트로 ~2GB가 됩니다.

이 KV 캐시를 효율적으로 관리하는 것은 어려운 작업입니다. 배치 크기와 시퀀스 길이에 따라 선형적으로 증가하기 때문에 메모리 요구 사항이 빠르게 확장될 수 있습니다. 따라서 제공할 수 있는 처리량이 제한되고 긴 컨텍스트 입력에 대한 문제가 발생합니다. 이번 포스트에 소개된 몇 가지 최적화 기술의 동기는 바로 이 때문입니다.

LLM을 모델 병렬화와 함께 스케일업 하기

모델 가중치의 장치당 메모리 풋프린트를 줄이는 한 가지 방법은 모델을 여러 GPU에 분산하는 것입니다. 메모리와 연산 공간을 분산하면 더 큰 모델 또는 더 많은 입력 배치를 실행할 수 있습니다. 모델 병렬화는 단일 장치에서 사용할 수 있는 것보다 더 많은 메모리가 필요한 모델을 학습 또는 추론하고 특정 사용 사례에 적합한 학습 시간 및 추론 측정값(지연 시간 또는 처리량)을 만들기 위해 반드시 필요합니다. 모델 가중치를 분할하는 방식에 따라 모델을 병렬화하는 방법에는 여러 가지가 있습니다. 

데이터 병렬화는 아래에 나열된 다른 기법들과 같은 맥락에서 자주 언급되는 기법이기도 합니다. 이 경우 모델의 가중치가 여러 장치에 복사되고 입력의 (전역) 배치 크기가 각 장치에 걸쳐 마이크로 배치로 분할됩니다. 더 큰 배치를 처리하여 전체 실행 시간을 단축합니다. 그러나 추론과는 관련성이 떨어지는 훈련 시간 최적화입니다.

파이프라인 병렬화

파이프라인 병렬화는 모델을 (수직적으로) 청크로 분할하는 작업이 포함되며, 각 청크는 별도의 장치에서 실행되는 레이어의 서브셋으로 구성됩니다. 그림 2a는 모델이 순차적으로 분할되고 모든 레이어의 1/4 서브셋이 각 장치에서 실행되는 4방향 파이프라인 병렬 처리의 예시입니다. 한 디바이스의 연산 그룹의 출력들은 다음 디바이스로 전달되고, 다음 디바이스는 후속 청크를 계속 실행합니다.  F_nB_n 는 각각 장치 n에서 정방향 및 역방향 패스를 나타냅니다. 각 장치에 모델 가중치를 저장하는 데 필요한 메모리 요구 사항은 효과적으로 4분의 1로 줄어듭니다.

이 방법의 주요 한계는 처리의 순차적 특성으로 인해 일부 디바이스나 레이어가 이전 레이어의 출력(활성화, 그라데이션)을 기다리는 동안 유휴 상태로 남아있을 수 있다는 점입니다. 이로 인해 정방향 및 역방향 패스 모두에서 비효율성 또는 “파이프라인 버블”이 발생합니다. 그림 2b에서 흰색 빈 영역은 디바이스가 유휴 상태이고 활용도가 낮은 파이프라인 병렬 처리로 인한 큰 파이프라인 버블입니다.

그림 2c에서 볼 수 있듯이 마이크로배치는 이 문제를 어느 정도 완화할 수 있습니다. 입력의 전역 배치 크기는 하위 배치로 분할되어 하나씩 처리되며 마지막에 그라데이션이 누적됩니다. m 마이크로배치가 있는 n 디바이스에서 F_{n,m}B_{n,m}은 각각 정방향 및 역방향 패스를 나타냅니다. 이 접근 방식은 파이프라인 버블의 크기를 줄이기는 하지만 완전히 제거하지는 못합니다.

그림 2. 4방향 파이프라인 병렬 처리의 그림. Credit: GPipe: Easy Scaling with Micro-Batch Pipeline Parallelism

텐서 병렬화

텐서 병렬화는 모델의 개별 레이어를 서로 다른 장치에서 실행할 수 있는 더 작고 독립적인 계산 블록으로 (수평적으로) 샤딩하는 작업이 포함됩니다. 어텐션 블록과 멀티-레이어 퍼셉트론(MLP) 레이어는 텐서 병렬화를 활용할 수 있는 트랜스포머의 주요 구성 요소입니다. 멀티-헤드 어텐션 블록에서는 각 헤드 또는 헤드 그룹을 다른 장치에 할당하여 독립적으로 병렬로 계산할 수 있습니다.

그림 3. 다중 레이어 퍼셉트론(MLP) 및 자체 어텐션 레이어의 텐서 병렬 처리 Credit: Megatron-LM: Training Multi-Billion Parameter Language Models Using Model Parallelism

그림 3a는 2계층 MLP에서 양방향 텐서 병렬 처리의 예를 보여 주며, 각 계층은 둥근 상자로 표시되어 있습니다. 첫 번째 레이어 내에서 가중치 행렬 AA_1A_2으로 나뉩니다. 연산  XA_1XA_2은 두 개의 다른 장치에서 입력 X의 동일한 배치(f은 아이덴티티 연산)에서 독립적으로 실행될 수 있습니다. 이렇게 하면 각 장치에 가중치를 저장하는 데 필요한 메모리가 효과적으로 절반으로 줄어듭니다. 감소 연산 g 은 두 번째 계층의 출력을 결합합니다.

그림 3b는 셀프 어텐션 레이어에서 양방향 텐서 병렬 처리의 예시입니다. 멀티 어텐션 헤드는 본질적으로 병렬이며 디바이스 간에 분할할 수 있습니다.

시퀀스 병렬화

텐서 병렬화는 레이어를 독립적이고 관리 가능한 블록으로 분할해야 한다는 한계가 있습니다. 대신 텐서 병렬 그룹 전체에 복제되는 LayerNorm 및 Dropout과 같은 연산에는 적용되지 않습니다. LayerNorm과 Dropout은 연산 비용이 저렴하지만, (중복) 액티베이션을 저장하는 데 상당한 양의 메모리가 필요합니다.

Reducing Activation Recomputation in Large Transformer Models에서 볼 수 있듯이 이러한 연산은 입력 시퀀스 전체에서 독립적이며, 이러한 연산은 ‘시퀀스-차원’을 따라 분할할 수 있어 메모리 효율성을 높일 수 있습니다. 이를 시퀀스 병렬 처리라고 합니다.

그림 4. 텐서 및 시퀀스 병렬 처리가 모두 적용된 트랜스포머 레이어의 그림. Credit: Reducing Activation Recomputation in Large Transformer Models

모델 병렬화를 위한 기법은 배타적이지 않으며 함께 사용할 수 있습니다. 이러한 기법은 LLM의 GPU당 메모리 풋프린트를 확장하고 줄이는 데 도움이 될 수 있지만, 어텐션 모듈을 위한 최적화 기법도 있습니다.

어텐션 메커니즘 최적화 하기 

스케일드 닷-프로덕트 어텐션(SDPA) 작업은 Attention Is All You Need에서 설명한 대로 쿼리 및 키-밸류 쌍을 출력에 매핑합니다.

멀티-헤드 어텐션

SDPA의 향상된 기능으로, 어텐션 레이어를 학습된 서로 다른 Q, K, V 행렬의 프로젝션과 병렬로 여러 번 실행하면 모델이 서로 다른 위치에서 서로 다른 표현 하위 공간의 정보에 함께 참여할 수 있습니다. 이러한 하위 공간은 독립적으로 학습되므로 모델은 입력의 다양한 위치에 대해 더 풍부하게 이해할 수 있습니다.

그림 5에 표시된 것처럼 여러 병렬 어텐션 작업의 출력은 연결되고 선형적으로 투영되어 결합됩니다. 각각의 병렬 어텐션 레이어를 ‘헤드’라고 하며, 이 접근 방식을 멀티 헤드 어텐션(MHA)라고 합니다.

원래 작업에서 각 어텐션 헤드는 8개의 병렬 어텐션 헤드를 사용할 때 모델의 축소된 차원(예: d_{model}/8)에서 작동합니다. 이렇게 하면 계산 비용이 싱글-헤드 어텐션과 비슷하게 유지됩니다.

그림 5. 스케일드 닷-프로덕트 어텐션(SDPA, 왼쪽)와 멀티-헤드 어텐션(오른쪽)의 그림으로, 단순히 여러 개의 SDPA 헤드를 병렬로 배치한 것임. Credit: Attention Is All You Need

멀티-쿼리 어텐션

Fast Transformer Decoding에서 제안된 멀티 쿼리 어텐션(MQA)라고 하는 MHA에 대한 추론 최적화 중 하나는 여러 어텐션 헤드 간에 키와 밸류를 공유하는 방식입니다. 쿼리 벡터는 이전과 마찬가지로 여전히 여러 번 투영됩니다. 

MQA에서 수행되는 계산의 양은 MHA와 동일하지만 메모리에서 읽는 데이터(키, 밸류)의 양은 이전의 일부에 불과합니다. 메모리 대역폭에 구애받지 않으므로 컴퓨팅 활용도를 높일 수 있습니다. 또한 메모리 내 KV 캐시의 크기가 줄어들어 더 큰 배치 크기를 위한 공간을 확보할 수 있습니다.

키-밸류 헤드의 감소는 잠재적인 정확도 저하를 수반합니다. 또한 추론 시 이 최적화를 활용해야 하는 모델은 MQA를 활성화한 상태에서 훈련(또는 최소한 훈련량의 ~5%로 미세 조정)을 수행해야 합니다.

그룹 – 쿼리 어텐션 

Grouped-query attention (GQA)는 키와 밸류를 몇 개의 쿼리 헤드 그룹에 투사하여 MHA와 MQA 간의 균형을 맞춥니다(그림 6). 각 그룹 내에서는 멀티 쿼리 어텐션처럼 작동합니다. 

그림 6은 멀티 헤드 어텐션이 여러 개의 키-밸류 헤드를 가지고 있음을 보여줍니다(왼쪽). 그룹 쿼리 어텐션(가운데)은 키-밸류 헤드가 하나보다 많지만 쿼리 헤드 수보다는 적어 메모리 요구 사항과 모델 품질 간에 균형을 이룹니다. 멀티-쿼리 어텐션(오른쪽)은 단일 키-밸류 헤드를 사용하여 메모리를 절약할 수 있습니다.

그림 6. 다양한 어텐션 메커니즘 비교. Credit: GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints

원래 MHA로 학습된 모델은 원래 학습 컴퓨팅의 일부를 사용하여 GQA로 ‘업트레이닝’할 수 있습니다. MQA에 가까운 계산 효율을 유지하면서 MHA에 가까운 품질을 얻을 수 있습니다. Llama 2 70B 는 GQA를 활용하는 모델의 예입니다.

MQA 및 GQA와 같은 최적화 기법은 저장되는 키 및 밸류 헤드의 수를 줄임으로써 KV 캐시에 필요한 메모리를 줄이는 데 도움이 됩니다. 하지만 이 KV 캐시가 관리되는 방식에는 여전히 비효율성이 있을 수 있습니다. 다음 섹션에서는 어텐션 모듈 자체를 최적화하는 것과는 다른 방식으로 보다 효율적인 KV 캐시 관리를 위한 기술을 소개합니다.

플래시 어텐션

어텐션 메커니즘을 최적화하는 또 다른 방법은 GPU의 메모리 계층 구조를 더 잘 활용하기 위해 특정 계산의 순서를 수정하는 것입니다. 신경망은 일반적으로 레이어로 설명되며, 대부분의 구현도 입력 데이터에 대해 한 번에 한 가지 종류의 연산을 순서대로 수행하여 이러한 방식으로 배치됩니다. 이 방식이 항상 최적의 성능으로 이어지는 것은 아니며, 이미 더 높은 수준의 메모리 계층 구조로 가져온 값에 대해 더 많은 계산을 수행하는 것이 유리할 수 있기 때문입니다. 

실제 계산 중에 여러 계층을 융합하면 GPU가 메모리를 읽고 쓰는 횟수를 최소화하고 신경망의 다른 계층에 속해 있더라도 동일한 데이터가 필요한 계산을 함께 그룹화할 수 있습니다.

FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness에 자세히 설명된 대로 I/O 인식하는 정확한 어텐션 알고리즘인 플래시 어텐션은 매우 인기 있는 퓨전 기술 중 하나입니다. 정확한 어텐션은 표준 멀티-헤드 어텐션(다중 쿼리 및 그룹 쿼리 어텐션에 사용할 수 있는 변형 포함)와 수학적으로 동일하므로 기존 모델 아키텍처 또는 이미 학습된 모델에 수정 없이 교체할 수 있음을 의미합니다.

I/O 인식하는(I/O aware)의 뜻은 연산을 융합할 때 앞서 설명한 메모리 이동 비용을 일부 고려한다는 의미입니다. 특히, 플래시 어텐션은 전체 행렬의 일부 연산을 단계적으로 수행하고 그 사이에 중간 값을 기록하는 대신, 최종 행렬의 작은 부분을 한 번에 완전히 계산하고 기록하는 ‘타일링’ 방식을 사용합니다.

그림 7은 40GB GPU의 타일형 플래시어텐션 계산 패턴과 메모리 계층구조를 보여줍니다. 오른쪽 차트는 어텐션 메커니즘의 여러 구성 요소를 융합하고 재정렬하여 얻을 수 있는 상대적인 속도 향상을 보여줍니다.

그림 7. 40GB GPU의 타일형 플래시어텐션 계산 패턴과 메모리 계층구조. Credit: FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness

페이징을 통한 KV 캐시의 효율적인 관리

때때로 입력의 크기를 예측할 수 없기 때문에 가능한 최대 입력(지원되는 시퀀스 길이)을 고려하기 위해 KV 캐시가 정적으로 “오버 프로비저닝”되는 경우가 있습니다. 예를 들어, 모델의 지원되는 최대 시퀀스 길이가 2,048인 경우, 입력 크기와 요청에서 생성된 출력에 관계없이 메모리에 2,048 크기의 예약이 이루어집니다. 이 공간은 연속적으로 할당될 수 있으며, 종종 많은 부분이 사용되지 않은 채 남아 있어 메모리 낭비 또는 조각화로 이어질 수 있습니다. 이 예약된 공간은 요청의 수명 기간 동안 묶여 있습니다.

그림 8. 오버프로비저닝과 비효율적인 KV 캐시 관리로 인한 메모리 낭비 및 조각화의 예시. Credit: Efficient Memory Management for Large Language Model Serving with PagedAttention

운영 체제의 페이징에서 영감을 얻은  PagedAttention 알고리즘은 연속적인 키와 밸류를 메모리의 비연속 공간에 저장할 수 있게 해줍니다. 이 알고리즘은 각 요청의 KV 캐시를 고정된 수의 토큰을 나타내는 블록으로 분할하여 비연속적으로 저장할 수 있습니다. 

이러한 블록은 어텐션 연산 중에 필요에 따라 계정을 유지하는 블록 테이블을 사용하여 가져옵니다. 새로운 토큰이 생성되면 새로운 블록이 할당됩니다. 이러한 블록의 크기는 고정되어 있어 서로 다른 요청이 서로 다른 할당을 요구하는 등의 문제로 인해 발생하는 비효율을 제거합니다. 이는 메모리 낭비를 크게 제한하여 더 큰 배치 크기(결과적으로 처리량)를 가능하게 합니다.

모델 최적화 기술들

지금까지 LLM이 메모리를 소비하는 다양한 방법, 메모리를 여러 GPU에 분산할 수 있는 몇 가지 방법, 어텐션 메커니즘 및 KV 캐시 최적화에 대해 설명했습니다. 또한 모델 가중치 자체를 수정하여 각 GPU의 메모리 사용량을 줄이는 몇 가지 모델 최적화 기법도 있습니다. 또한 GPU에는 이러한 수정된 값에 대한 연산을 가속화하는 전용 하드웨어가 있어 모델 속도를 훨씬 더 높일 수 있습니다.

양자화 (Quantization)

양자화(Quantization)는 모델의 가중치와 활성화의 정밀도를 낮추는 프로세스입니다. 대부분의 모델은 32비트 또는 16비트의 정밀도로 학습되며, 각 파라미터와 활성화 요소는 단정밀도 부동 소수점인 32비트 또는 16비트의 메모리를 차지합니다. 하지만 대부분의 딥러닝 모델은 밸류 당 8비트 또는 그 이하로도 효과적으로 표현할 수 있습니다.

그림 9는 가능한 한 가지 양자화 방법 전후의 값 분포를 보여줍니다. 이 경우 반올림으로 인해 일부 정밀도가 손실되고 클리핑으로 인해 일부 동적 범위가 손실되어 값이 훨씬 작은 형식으로 표현될 수 있습니다.

그림 9. 한 가지 가능한 양자화 전후의 값 분포

모델의 정밀도를 낮추면 여러 가지 이점을 얻을 수 있습니다. 모델이 메모리 공간을 덜 차지하면 같은 양의 하드웨어에 더 큰 모델을 넣을 수 있습니다. 또한 양자화는 동일한 대역폭에서 더 많은 매개변수를 전송할 수 있다는 것을 의미하므로 대역폭이 제한된 모델을 가속화하는 데 도움이 될 수 있습니다.

활성화, 가중치 또는 두 가지 모두에 대한 정밀도를 낮추는 LLM의 양자화 기법에는 여러 가지가 있습니다. 가중치는 훈련 후에 고정되기 때문에 양자화하는 것이 훨씬 더 간단합니다. 하지만 액티베이션이 더 높은 정밀도로 유지되기 때문에 성능이 저하될 수 있습니다. GPU에는 INT8 및 FP16 숫자를 곱하기 위한 전용 하드웨어가 없으므로 실제 연산을 위해 가중치를 더 높은 정밀도로 다시 변환해야 합니다.

활성화, 트랜스포머 블록 및 네트워크 레이어의 입력을 양자화할 수도 있지만 여기에는 고유한 어려움이 따릅니다. 액티베이션 벡터에는 종종 이상값이 포함되어 있어 동적 범위를 효과적으로 증가시키고 가중치보다 낮은 정밀도로 이러한 값을 표현하기가 더 어렵습니다. 

한 가지 옵션은 대표 데이터 세트를 모델에 전달하고 특정 활성화를 다른 활성화보다 더 높은 정밀도로 표현하도록 선택하여 이러한 이상값이 나타날 가능성이 있는 위치를 찾는 것입니다(LLM.int8()). 또 다른 옵션은 양자화하기 쉬운 가중치의 동적 범위를 빌려서 활성화에 해당 범위를 재사용하는 것입니다.

희소성(Sparsity)

양자화와 유사하게, 많은 딥 러닝 모델이 프루닝을 하거나 0에 가까운 특정 값을 0 자체로 대체하는 데 강한 것으로 나타났습니다. 희소 행렬은 많은 요소가 0인 행렬로, 전체 밀도 행렬보다 공간을 덜 차지하는 압축된 형태로 표현할 수 있습니다.

그림 10. 0이 아닌 데이터 값과 해당 2비트 인다이스로 구성된 압축 형식으로 표현되는 희소 행렬

특히 GPU는 네 개의 값 중 두 개가 0으로 표현되는 특정 종류의 구조적 희소성(structured sparsity)에 대한 하드웨어 가속 기능을 갖추고 있습니다. 또한 희소 표현을 양자화와 결합하면 실행 속도를 훨씬 더 높일 수 있습니다. 대규모 언어 모델을 희소 형식으로 표현하는 가장 좋은 방법을 찾는 것은 여전히 활발한 연구 분야이며 향후 추론 속도를 개선할 수 있는 유망한 방향을 제시합니다.

증류 (Distillation)

모델의 크기를 줄이는 또 다른 접근 방식은 증류라는 프로세스를 통해 지식을 더 작은 모델로 이전하는 것입니다. 이 과정에는 더 작은 모델(학생이라고 함)이 더 큰 모델(교사)의 행동을 모방하도록 훈련하는 것이 포함됩니다.

증류 모델의 성공적인 예로는 BERT 모델을 40% 압축하면서도 언어 이해 능력의 97%를 60% 더 빠른 속도로 유지하는 DistilBERT가 있습니다.

LLM에서의 증류는 활발한 연구 분야이지만, 신경망에 대한 일반적인 접근 방식은 Distilling the Knowledge in a Neural Network에서 처음 설명했습니다:

  • 학생 네트워크는 출력 간의 불일치를 측정하는 손실 함수를 사용하여 더 큰 교사 네트워크의 성능을 반영하도록 학습됩니다. 이 목표는 학생의 출력을 실측 레이블과 일치시키는 원래의 손실 함수를 잠재적으로 포함하는 것 외에도 추가됩니다.
  • 일치하는 교사의 출력은 가장 마지막 레이어(로짓- logits라고 함) 또는 중간 레이어 활성화가 될 수 있습니다.

그림 11은 지식 증류의 일반적인 프레임워크를 보여줍니다. 교사의 로짓은 학생이 증류 손실을 사용하기 위해 최적화하는 소프트 타깃입니다. 다른 증류 방법에서는 다른 손실 측정값을 사용하여 교사의 지식을 ‘증류’할 수 있습니다.

그림 11. 지식 증류를 위한 일반적인 프레임워크. Credit: Knowledge Distillation: A Survey

증류에 대한 또 다른 접근 방식은 교사가 학생 LLM의 감독 교육을 위해 합성한 데이터를 사용하는 것인데, 이는 사람의 주석이 부족하거나 사용할 수 없을 때 특히 유용합니다. ‘Distilling Step by Step!’는 한 걸음 더 나아가 기준 자료 역할을 하는 레이블에 더해 교사 LLM에서 근거를 추출합니다. 이러한 근거는 데이터 효율적인 방식으로 소규모 학생 LLM을 학습시키기 위한 중간 추론 단계 역할을 합니다.

오늘날 많은 최첨단 LLM은 다른 LLM을 학습하는 데 결과물을 사용하는 것을 금지하는 제한적인 라이선스를 가지고 있어 적합한 교사 모델을 찾기가 어렵다는 점에 유의하는 것이 중요합니다.

모델 서빙 기술 

모델 실행은 종종 메모리 대역폭, 특히 가중치에서 대역폭에 제한을 받습니다. 앞서 설명한 모든 모델 최적화를 적용한 후에도 여전히 메모리가 제한될 가능성이 매우 높습니다. 따라서 모델 가중치가 로드될 때 가능한 한 많은 작업을 수행해야 합니다. 다시 말해, 병렬로 작업을 수행해 보세요. 두 가지 접근 방식을 취할 수 있습니다:

  • 인-플라이트 배칭(In-flight batching)에는 여러 개의 서로 다른 요청을 동시에 실행하는 것이 포함됩니다. 
  • 추측 추론(Speculative inference)은 시간을 절약하기 위해 시퀀스의 여러 다른 단계를 병렬로 실행하는 것을 포함합니다.

인-플라이트 배칭(In-flight batching)

LLM에는 몇 가지 고유한 실행 특성이 있어 실제로 요청을 효과적으로 일괄 처리하기 어려울 수 있습니다. 하나의 모델을 서로 매우 다르게 보이는 다양한 작업에 동시에 사용할 수 있습니다. 챗봇의 간단한 질의응답부터 문서 요약 또는 긴 코드 덩어리 생성에 이르기까지 워크로드는 매우 동적이며, 출력의 크기가 몇 배나 달라집니다.

이러한 다양성으로 인해 요청을 배치하고 병렬로 효과적으로 실행하는 것이 어려울 수 있는데, 이는 신경망 서빙을 위한 일반적인 최적화입니다. 이로 인해 일부 요청이 다른 요청보다 훨씬 일찍 완료될 수 있습니다.

이러한 동적 부하를 관리하기 위해 많은 LLM 서빙 솔루션에는 연속 또는 인-플라이트 배칭이라는 최적화된 스케줄링 기술이 포함되어 있습니다. 이는 LLM의 전체 텍스트 생성 프로세스가 모델에서 여러 번의 실행 반복으로 세분화될 수 있다는 사실을 활용합니다.

인플라이트 배치에서는 전체 배치가 완료될 때까지 기다렸다가 다음 요청 세트로 넘어가는 대신 서버 런타임이 완료된 시퀀스를 배치에서 즉시 퇴출합니다. 그런 다음 다른 요청이 아직 전송 중인 동안 새 요청을 실행하기 시작합니다. 따라서 인플라이트 배치는 실제 사용 사례에서 전체 GPU 활용도를 크게 높일 수 있습니다.

추측 추론(Speculative inference)

추측 샘플링, 보조 생성 또는 블록 단위 병렬 디코딩이라고도 하는 추측 추론은 LLM 실행을 병렬화하는 다른 방식입니다. 일반적으로 GPT 스타일의 거대 언어 모델은 토큰 단위로 텍스트를 생성하는 자동 회귀 모델입니다.

생성되는 모든 토큰은 컨텍스트를 제공하기 위해 그 앞에 오는 모든 토큰에 의존합니다. 즉, 일반적인 실행에서는 동일한 시퀀스에서 여러 개의 토큰을 동시에 생성하는 것이 불가능하며, n번째 토큰이 생성될 때까지 기다려야만 n+1을 생성할 수 있습니다.

그림 12는 초안 모델이 동시에 확인되거나 거부되는 여러 다음 단계를 일시적으로 예측하는 추측 추론의 예를 보여줍니다. 이 경우 초안에서 처음 두 개의 예측 토큰은 승인되고 마지막 토큰은 거부되어 생성을 계속하기 전에 제거됩니다.

그림 12. 추측 추론(speculative inference)의 예. Credit: Blockwise Parallel Decoding for Deep Autoregressive Models

추측 샘플링(Speculative sampling)은 해결 방법을 제공합니다. 이 접근법의 기본 아이디어는 몇 토큰 길이의 초안 연속을 생성하기 위해 “더 저렴한” 프로세스를 사용하는 것입니다. 그런 다음, 필요한 실행 단계에서 저렴한 초안을 “추측” 컨텍스트로 사용하여 여러 단계에서 주요 “검증” 모델을 병렬로 실행합니다.

검증 모델이 초안과 동일한 토큰을 생성하면 해당 토큰을 출력으로 수락해야 한다는 것을 알 수 있습니다. 그렇지 않으면 일치하지 않는 첫 번째 토큰 이후의 모든 토큰을 버리고 새 초안으로 프로세스를 반복할 수 있습니다.

초안 토큰을 생성하는 방법에는 여러 가지 옵션이 있으며, 각 옵션에는 서로 다른 장단점이 있습니다. 여러 모델을 학습시키거나 미리 학습된 단일 모델에서 여러 개의 헤드를 미세 조정하여 미래의 여러 단계의 토큰을 예측할 수 있습니다. 또는 작은 모델을 초안 모델로 사용하고 더 크고 성능이 뛰어난 모델을 검증자로 사용할 수도 있습니다.

결론

이 포스팅에서는 데이터센터 또는 PC의 엣지 단에서 LLM을 효율적으로 최적화하고 제공하는 데 도움이 되는 가장 인기 있는 솔루션에 대해 설명합니다. 이러한 기술 중 다수는 최적화된 커널, 전처리 및 후처리 단계, 멀티 GPU/멀티 노드 통신 프리미티브와 함께 TensorRT 딥 러닝 컴파일러로 구성된 오픈 소스 라이브러리인 NVIDIA TensorRT-LLM을 통해 최적화되어 제공되며, NVIDIA GPU에서 획기적인 성능을 발휘할 수 있습니다. 자세한 내용은 Optimizing Inference on Large Language Models with NVIDIA TensorRT-LLM, Now Publicly Available를 참조하세요.

이제 엔비디아 TensorRT-LLM은 NVIDIA Triton Inference Serve에서 지원되므로, 기업은 최대 처리량과 최소 지연 시간으로 다양한 AI 프레임워크, 하드웨어 가속기 및 배포 모델에 걸쳐 여러 AI 모델을 동시에 제공할 수 있습니다.

또한, TensorRT-LLM은  NVIDIA NeMo를 구동하여 개발자가 수십억 개의 파라미터로 제너레이티브 AI 모델을 빌드, 커스터마이징 및 배포할 수 있는 엔드투엔드 클라우드 네이티브 엔터프라이즈 프레임워크를 제공합니다. : NeMo 시작하기

관련 리소스

Discuss (0)

Tags