Ssul's Blog

LLM 모델 사이즈, 경량화/양자화(quantization) 본문

AI & ML/개념잡기

LLM 모델 사이즈, 경량화/양자화(quantization)

Ssul 2024. 3. 14. 10:56

1. 모델 사이즈 개념

요즘 AI모델이 나오면, 파라미터가 몇억개, 몇백억개, 몇조개라고 모델의 사이즈를 항상 언급된다.

여기서 그러면 파라미터는 무엇일까?

아래 그림을 기준으로 하면, 까만 점은 노드라고 보면 된다.

그리고 선이 파라미터라고 보면된다.

그렇다면 위 신경망의 사이즈는?

9개(3+3+3)+6개(2+2+2) =15개

파라미터가 15개인 신경망인 것이다.

 

그럼 gpt3은 175B라고 하니, 1750억개의 선이 있는 신경망으로 이해하면 되겠다.

 

2. AI모델과 필요한 그래픽카드

우선 컴퓨터 용량관련 산수!

1byte=8bit,

1KB=1024byte=1024*1byte

1MB=1024KB=1024*1024*1byte

1GB=1024MB=1024*1024*1024*1byte = 1,073,741,824byte = 약 10억byte

요약 1기가는 10억byte

 

자 그럼 7B모델은 어느정도 용량이 필요할까?

우선 7B는 70억개이니, 70억개의 파라미터를 가지고 있다.

그리고 각 파라미터는 4byte이니, 70억*4byte의 용량이 필요하다.

결론은 280억byte가 필요.

 

1GB가 10억byte이니, 280억byte는 28GB.

7B모델은 약 28GB의 메모리가 필요하다는 결론

 

여기서 끝나면 좋겠지만, AI를 트레이닝 한다면,

옵티마이저와 그라디언트가 필요하다.

옵티마이저는 모델의 4배 메모리,

그라디언트는 모델의 1배 메모리.

 

그러면, 내가 7B모델을 학습시킨다고 하면,

모델 28G메모리

옵티마이저 28*4=112G메모리

그라디언트 28G*1=28G메모리

해서, 총 168G의 메모리가 필요하다

(*여기에 배치사이즈가 커지면 더 커진다)

 

그래픽 카드로 넘어가보자. nvidia A100(80G)가 3,000만원이니,

168G를 커버하려면, 3장의 A100그래픽카드가 필요하다.

결론은 9천만원!!!

 

크지않은... 7B모델을 학습하는데... 1억이 필요하다 ㅜㅜ

(nvidia주식 아직도 늦지 않은 것인가.....)

 

175B는 7B의 25배이니, 25억,

한마디로 gpt-3규모의 모델을 내가 가진 인프라로 풀파인튜닝한다고 하면 25억이 필요하다는 것

 

3. 그럼 어떻게 해야함? Quantization(양자화)

한개의 파라미터가 32bit=4byte이다.

이를 8bit(1byte), 4bit(0.5byte)로 줄이는 것이 quantization이다.

그러면 기존에 168G가 필요했다면,

8bit로 양자화 하면 1/4이니 42G

4bit로 양자화 하면 1/8이니 24G 그래픽카드가 필요하다.

이러면, A100 40G로도 7B모델 학습이 가능하다.

 

코드로 한번 살펴보자.

거대한 PLM에 로라를 붙여서 학습을 시키는 코드.

여기서는 최근에 나온 구글의 gemma-7b(70억개 파라미터) 모델을 사용한다.

이미지 출처: LoRA논문

위 그림에서 파란색이 gemma-7b, 오른쪽 주황색이 LoRA이다.

 

우선 7B(70억)모델을 4bit로 quantization시켜서 가져오기 코드

# 기본 PLM
model_id = "google/gemma-7b"

# quantization 옵션 설정
bnb_config = BitsAndBytesConfig(
	#입력값을 4bit로 변환
    load_in_4bit=True,
    #모델을 4bit로 양자화
    bnb_4bit_quant_type="nf4",
    #4bit계산에 사용될 데이터유형, 4비트 부동소수점(bfloat16), 4비트정수(uint8)
    bnb_4bit_compute_dtype=torch.bfloat16
)

# 7B모델을 4비트로 양자화 하여 가져오니 1/8사이즈로 가져오는 것
model = AutoModelForCausalLM.from_pretrained(model_id, quantization_config=bnb_config)

#토크나이저는 원래처럼 가져오면 됩니다.
tokenizer = AutoTokenizer.from_pretrained(model_id)

 

왼쪽 파란색을 1/8사이즈로 줄여서 가져왔다. 이제 이 친구 오른쪽의 LoRA를 붙여보자

#LoRA옵션 값을 설정하자
from peft import LoraConfig

#LoRA옵션값 설정
lora_config = LoraConfig(
	#차원축소 개념으로 보통 8, 16설정 주황색의 가운데 부분
    r=lora_r,
    lora_alpha=lora_alpha,
    #LoRA를 붙이는 위치로, attention쪽, MLP쪽 등 내가 원하는 곳에 붙일수 있다
    target_modules=lora_target_modules,
    lora_dropout=lora_dropout,
    bias="none",
    task_type="CAUSAL_LM")
    
from peft import (
    get_peft_model,
    prepare_model_for_kbit_training
)

#LoRA를 붙일수 있게 셋팅
model = prepare_model_for_kbit_training(model)

#LoRA를 붙여서, 그림과 같은 모델 완성
model = get_peft_model(model, lora_config) # Applying LoRA

 

이렇게 셋팅될 model변수를 평소와 같이 학습을 시키면 된다.

평소에 7B모델을 학습하기위해 168G의 그래픽카드 메모리가 필요했다면,

이제는 4bit로 양자화 하여서 1/8인 21G의 그래픽카드 메모리만 있으면 학습이 가능해진다.

 

train인 생략하고....(다른 글에서 다루자)

 

학습을 마친 모델은 아래와 같은 코드로 저장한다

#내 컴퓨터에 저장
output_dir="내 로컬 컴퓨터 주소"
model.save_pretrained(output_dir)


#허깅페이스 저장
model_name = "내 허깅페이스 주소"
model.push_to_hub(model_name, use_auth_token=True)

 

 

반대로 학습을 마치고 완성해서 저장해놓은 코드는 아래와 같이 가져온다.

#내 컴퓨터에서
from peft import PeftModel

#기본모델 설정
base_model = AutoModelForCausalLM.from_pretrained(
    model_id,
    return_dict = True,
    torch_dtype=torch.float16,
    device_map=device)

#peftModel로 기본모델과 학습한 모델을 넣어주면, 완성
model = PeftModel.from_pretrained(base_model, output_dir, device)


#허깅페이스에서
rom peft import PeftModel, PeftConfig

peft_model_id = "내 허깅페이스 주소"

#기본 설정파일을 가져오고
config = PeftConfig.from_pretrained(peft_model_id)
#설정에서 기본모델PLM을 불러오고
model = AutoModelForCausalLM.from_pretrained(config.base_model_name_or_path)
#LoRA를 붙여서 완성한다
model = PeftModel.from_pretrained(model, peft_model_id)

 

(참고) 1/2로 줄이는 것도 가능하다. BitsAndBytesConfig를 사용안함

model = AutoModelForCausalLM.from_pretrained("google/gemma-2b-it", device_map="auto", torch_dtype=torch.float16)

model = AutoModelForCausalLM.from_pretrained("google/gemma-2b-it", device_map="auto", torch_dtype=torch.bfloat16)

이렇게 하면, 원래 파라미터를 float32로 표현하는데, 절반인 16bit로 표현하게 하는 구조임

 

그리고 float16과 bfloat16의 차이는,

float16: 1비트(부호) + 5비트(지수부) + 10비트(가수부)

bfloat16: 1비트(부호) + 8비트(지수부) + 7비트(가수부)

여기서 지수부는 숫자의 범위, 당연히 지수부가 높으면 더 넓은 범위의 숫자 표현가능,

가수부는 숫자의 표현범위. 당연히 가수부가 높으면 더 정밀한 숫자 표현가능

float32는 1비트(부호) + 8비트(지수부) + 23비트(가수부)임

 

4. 정리

원래 LLM모델은 용량이 엄청나게 크고, 학습/서빙하려면 엄청난 그래픽카드 용량이 필요하다.

하지만, 양자화를 한다면, 그나마 1/4, 1/8사이즈로 학습/서비스가 가능하게 된다!

 

아!!! 그리고, 학습이 아닌, inference시에는 모델 사이즈의 두배 그래픽카드가 필요하다고 생각하면 된다.

7B모델로 서비스를 한다면, 28G*2인 56G의 그래픽카드 용량이 필요하다.

물론 양자화를 통하면 서비스할때도 1/4, 1/8로 줄일수 있다.