이전 포스트에서는 LLM 인퍼런스에서 batching 기법의 중요성을 알아보았고, GPU util을 극대화하기 위한 batching 기법인 continuous batching에 대해서 알아보았습니다. continuous batching에 대해 자세히 알아보고 싶으신 분들은 이전 포스트를 참고하시면 좋을 것 같습니다.
오늘은 LLM 인퍼런스에서 메모리 효율화 기법 중 하나인 quantization에 대해서 다루고자 합니다.
Quantization과 인퍼런스 최적화
Quantization은 weight와 activation을 int8과 같은 낮은 precision 데이터 유형으로 표현하여 인퍼런스 실행의 계산 및 메모리 비용을 줄이는 기술입니다.
비트 수를 줄이는 것은 다양한 이점을 가져옵니다. 모델은 더 작은 메모리 공간을 필요로 하며, 에너지 소비가 감소합니다. 더불어, matmul과 같은 연산은 int 연산으로 인해 훨씬 효율적으로 수행될 수 있습니다. 이는 integer 데이터 유형만을 지원하는 디바이스에서도 모델을 실행할 수 있는 장점을 제공합니다.
Quantization의 기본 아이디어
Quantization의 기본 아이디어는 매우 단순합니다. 높은 precision으로 표현된 weight와 activation을 더 낮은 precision의 데이터 유형으로 변경하는 것입니다.
Accumulation 데이터 유형은 더하기, 곱하기 등의 누적 결과를 나타내는 유형입니다. 예를 들어, int8 값인 A=127과 B=127을 더한 결과인 C는 int8로 정확하게 표현할 수 없습니다. 따라서 전체적인 quantization 과정에서 정밀도 손실을 방지하기 위해서는 더 높은 precision 데이터 유형이 필요합니다.
Quantization의 일반적인 유형: float32 → float16 및 float32 → int8
일반적으로 Quantization은 float32 데이터를 float16 또는 int8로 변환하는 과정을 포함합니다.
Quantization to float16
float32를 float16으로 변환하는 과정은 비교적 간단합니다. 두 데이터 유형이 동일한 표현 스키마를 사용하기 때문입니다. 그러나 하드웨어가 float16을 지원하는지 여부를 고려해야 합니다. 예를 들어, 인텔 CPU는 float16을 스토리지 유형으로만 지원하며, 연산은 float32로 변환된 후에 이루어집니다. 또한, 모델의 연산이 낮은 precision에 민감한지 여부를 고려해야 합니다. 예를 들어, LayerNorm은 일반적으로 매우 작은 값(~1e-12)이기 때문에 float16 데이터 유형에서는 ~6e-5까지 표현될 때 NaN 이슈가 발생할 수 있습니다.
Quantization to int8
float32를 int8로 양자화하는 과정은 좀 더 tricky합니다. int8은 256개의 값만을 표현할 수 있으며, float32는 훨씬 더 넓은 범위의 값을 가집니다. 따라서 기본 아이디어는 [a, b] 범위의 float32 값을 int8 공간에 투영하는 것입니다.
Affine Quantization Scheme
주어진 [a, b] 범위의 float x에 대한 affine quantization scheme은 다음과 같이 표현됩니다.
\[ x = S \cdot (x_q - Z) \]
여기서:
- \(x_q\)는 x에 대한 int8로 양자화된 값,
- S는 scale 값으로, 양의 float32 값,
- Z는 zero-point로 불리는 값으로, float32 체계에서 0 값에 해당하는 int8 값입니다. ML 모델 전반에 걸쳐 사용되기 때문에 0 값을 정확히 표현할 수 있는 것이 중요합니다.
[a, b] 범위로 양자화된 값 \(x_q\)는 다음과 같은 연산으로 얻을 수 있습니다:
\[ x_q = \text{round}\left(\frac{x}{S} + Z\right) \]
\[ x_q = \text{clip}\left(\text{round}\left(\frac{x}{S} + Z\right), \text{round}\left(\frac{a}{S} + Z\right), \text{round}\left(\frac{b}{S} + Z\right)\right) \]
여기서 [a, b] 범위를 넘어가는 float32 값은 가장 가까운 표현값으로 클립됩니다.
Symmetric Quantization Scheme
Symmetric quantization scheme은 [a, b] 범위를 int8로 매핑하는 affine quantization scheme의 특수한 경우입니다. 이 경우, float 값 [0, a]의 대칭 범위를 고려합니다. 일반적으로 integer 영역은 [-127, 127]이며, int8 범위가 [-128, 127]이지만 여기서 -128을 제외합니다. 이는 범위를 대칭으로 설정함으로써 한 가지 값을 잃지만 추가적인 계산을 생략할 수 있어 속도 향상을 제공합니다.
Per-Tensor and Per-Channel Quantization
목표로 하는 정확도/지연 트레이드오프에 따라 양자화 매개변수의 세분성을 다르게 설정할 수 있습니다.
Per-Tensor Quantization
- 양자화 매개변수는 텐서별로 계산됩니다. 즉, 각 텐서에 대해 하나의 (S, Z) 쌍이 사용됩니다.
- 텐서별 양자화는 전체 텐서에 대해 하나의 스케일 및/또는 제로 포인트가 있음을 의미합니다.
Per-Channel (Per-Axis) Quantization
- 양자화 매개변수는 채널별로 계산됩니다. 즉, 텐서의 특정 차원에 대한 각 요소에 대해 (S, Z) 쌍을 저장할 수 있습니다. 예를 들어, [N, C, H, W] 모양의 텐서에 대해 두 번째 차원에 대한 채널별 양자화 매개변수를 사용하는 경우 C개의 (S, Z) 쌍을 갖게 됩니다.
- 이 방식은 정확도를 향상시킬 수 있지만 더 많은 메모리를 필요로 합니다.
Calibration
이전 섹션에서는 float32에서 int8로의 양자화가 어떻게 작동하는지 설명했지만, 한 가지 질문이 남아 있습니다. float32 값의 [a, b] 범위는 어떻게 결정되나요? 이러한 값의 범위를 지정하는 것이 calibration입니다.
Calibration은 float32 범위가 계산되는 양자화 중의 단계입니다. weights의 경우 실제 범위가 양자화 시간에 알려지기 때문에 매우 쉽습니다. 그러나 activation에 대해서는 덜 명확하며 다양한 접근 방식이 존재합니다.
- Post Training Dynamic Quantization
- 각 activation 범위는 런타임 시 즉시 계산됩니다.
- 이는 많은 작업 없이도 훌륭한 결과를 제공하지만 매번 범위를 계산할 때 발생하는 오버헤드로 인해 정적 양자화보다 약간 느릴 수 있습니다.
- Post Training Static Quantization
- 각 activation 범위는 보통 모델을 통해 대표 데이터를 전달하고 activation 값을 기록하여 양자화 시간에 미리 계산됩니다.
- 실제 단계는 다음과 같습니다.
- Observer는 activations을 기록합니다.
- Calibration 데이터셋에서 특정 수의 정방향 전파를 수행합니다.
- 일부 calibration 기법에 따라 각 연산의 범위가 계산됩니다.
- Quantization Aware Training
- 각 activation의 범위는 post training static quantization과 동일한 아이디어에 따라 training 시간에 계산됩니다.
post training quantization과 quantization aware training 두 방법 모두 calibration 기법을 정의하는 것이 필요합니다. 일반적인 방법들은 다음과 같습니다:
- Min-Max: 계산된 범위는 `[min observed value, max observed value]` 입니다. weights에 잘 적용됩니다.
- Moving Average Min-Max: 계산된 범위는 `[moving average min observed value, moving average max observed value]`입니다. activations에 잘 적용됩니다.
- Histogram: 값의 히스토그램과 최소값 및 최대값을 기록한 다음 일부 기준에 따라 선택합니다.
- Entropy: full-precision과 양자화된 데이터 간의 오류를 최소화하는 범위로 계산됩니다.
- Mean Square Error: full-precision과 양자화된 데이터 간의 mean square error를 최소화하는 범위로 계산됩니다.
- Percentile: 관찰된 값에 대한 주어진 백분위수 값 p를 사용하여 계산됩니다. 이는 affine 양자화를 수행할 때 가능하지만 symmetric 양자화를 수행할 때 항상 정확하게 일치하는 것은 아닙니다.
bitsandbytes vs. auto-GPTQ 비교
bitsandbytes와 auto-gptq는 quantization 하기 위해 가장 많이 사용되는 도구들입니다. 아래는 각각의 특징과 장점입니다.
bitsandbytes
- 사용하기 쉬움
- 양자화된 모델을 calibrating할 필요가 없어 모든 모델을 양자화하기 용이함.
- `torch.nn.Linear` 모듈이 포함되어 있으면 어떤 모델이든 즉시 양자화 가능.
- 여러 Modality에 상호적용 가능
- torch.nn.Linear layer를 포함하는 어떤 모델이든 양자화 가능, Whisper, ViT, Blip2 등 다양한 modality의 모델 양자화 가능.
- 어댑터 적용 시 성능 저하 없음
- 양자화된 베이스 모델에 어댑터를 학습하는 경우, 추론 성능 저하 없이 배포를 위해 기본 모델 위에 어댑터를 병합 가능.
- 역양자화된 모델 위에 어댑터를 병합 가능.
auto-GPTQ
- 텍스트 생성 속도가 빠름
- GPTQ 양자화 모델은 텍스트 생성에서 bitsandbytes 양자화 모델에 비해 빠름.
- n-bit 지원
- GPTQ 알고리즘은 최대 2bits까지 모델을 양자화 가능.
- 품질 저하가 있을 수 있음.
- Easily-Serializable
- GPTQ 모델은 어떤 수의 비트로도 직렬화 가능.
- AMD 지원
bitsandbytes vs. auto-GPTQ benchmark
아래는 https://huggingface.co/blog/overview-quantization-transformers 에 소개된 벤치마크 자료입니다.
Inference speed (forward pass only)
벤치마크를 통해 알 수 있었던 결과는 bitsandbytes와 GPTQ가 동일한 성능을 보이며, 대규모 배치 크기에서는 GPTQ가 약간 더 빠르다는 것입니다.
Generate speed
두 가지 벤치마크를 통해 attention caching을 사용하면 생성 속도가 더 빨라진다는 결론을 도출했습니다. 특히, GPTQ는 일반적으로 bitsandbytes보다 빠른 성능을 보입니다. 예를 들어, Batch_size=4 및 use_cache=True를 사용하면 속도가 두 배 빨라진다는 결과를 얻었습니다.
Hardware
Performance degradation
오늘은 LLM 추론 및 학습시에 메모리를 효율적으로 사용할 수 있는 기법 중 하나인 양자화에 대해서 다루었습니다. 양자화는 모델의 크기를 축소하고 추론 속도를 향상시키는 기술로, 정확성과 효율성 간의 균형을 찾는 과정입니다. 가장 많이 사용되는 양자화 도구는 bitsandbytes와 GPTQ로, 각각의 특징을 고려하여 효과적인 모델 배포와 성능 최적화를 달성할 수 있습니다.
다음 포스트에서는 다른 효율화 방법들에 대해서 다뤄보겠습니다.
감사합니다.
'AI > 모델 인퍼런스' 카테고리의 다른 글
Llama.cpp, GGUF 포맷, 그리고 양자화(Quantization) (2) | 2024.05.28 |
---|---|
주요 LLM API 비용 정리 (ChatGPT, Gemini, Claude, HyperClova + LLaMA3) (0) | 2024.05.11 |
LLM 인퍼런스 훑어보기 (5) - continuous batching (0) | 2023.11.10 |
LLM 인퍼런스 훑어보기 (4) - kernel fusion (0) | 2023.10.14 |
LLM 인퍼런스 훑어보기 (3) - KV Cache (deep dive) (0) | 2023.09.23 |