카테고리 없음

허깅페이스에서 오픈소스 모델로 양자화기법을 적용하여 요약 모델 구현하기

thrcle 2025. 2. 1. 00:35

안녕하세요 오니입니다🐰

Transformers 라이브러리는 자연어 처리를 위한 많은 태스크를 지원 및 직접 정의한 헤더를 부착하여 원하는 태스크를 수행할 수 있는 확장성을 가지고 있어 굉장히 유용한 라이브러리인데요. 요즘은 gpt API를 활용하여 여러 업무에 활용하고 있지만, 단순히 LLM실습을 해보거나 때로는 오픈소스 모델을 시도/활용해야하는 상황도 분명 존재합니다. 

(실제로 회사에서 답변을 요약하는 과제를 수행했을 때 오픈소스 모델로 먼저 테스트를 해봤습니다. 하지만 실제 업무에 활용하기에는 데이터양으로 인한 속도 이슈로 인해 GPT4o-mini를 사용했답니다😝 그럼에도 분명 의미가 있는 과정이었어요👍)

 

따라서 이번 포스팅에서는 허깅페이스에서 오픈소스 모델인 'Ko-Qwen2-7B-Instruct' 모델을 활용하는 방법에 대해 다뤄보겠습니다. 


Part 1. Transformers라이브러리의 주요 클래스 익히기

이번 실습에서 진행할 spow12/Ko-Qwen2-7B-Instruct모델은 허깅페이스에 접속하면 상세히 나와있습니다.

https://huggingface.co/spow12/Ko-Qwen2-7B-Instruct

 

spow12/Ko-Qwen2-7B-Instruct · Hugging Face

Ko-Qwen2-7B-Instruct Model Description This model is a Supervised fine-tuned version of Qwen2-7B -Instruct with DeepSpeed and trl for korean. Trained Data Trained with public data and private data and Generated data (about 50k) Usage from transformers impo

huggingface.co

접속해보면 사용할 수 있는 샘플 코드도 나와있어서 바로 활용할 수 있습니다.

(에러 발생시에는 적절히 GPT를 활용하면서 수정을 했습니다.😎)

 

Tokenizer

  •  토큰화는 입력된 텍스트를 모델이 처리할 수 있는 최소 단위로 변환하는 과정입니다. 모델을 학습할 때 데이터를 토큰화하는 기능을 모아 높은 객체를 토크나이저라고 합니다. 전통적인 의미의 최소단위는 형태소이나, 모든 형태소에 대한 단어사전을 구축할 수 없기에 지속적으로 업데이트를 해야한다는 단점이 있습니다. 따라서 최근에는 이를 보완한 서브워드 기반 토크나이저가 등장했습니다.
💬 [참고]형태소 기반 자연어 처리 과정 
1. KoNLPy, MeCab 등 형태소 분석기를 사용해 문장 형태소 분리
2. 1번 과정에서 제대로 분리되지 못한 단어를 찾아 사용자 사전 구축
3. 1-2 과정을 반복해 사용자 사전 구축 후 최종 형태소 분리 진행
4. 3번 과정 중 접속사, 어미 같은 불용어 제거 
5. 정수로 인코딩하여 모델에서 사용

 

Tokenizer 다운로드 및 토큰화 작업을 수행해 줍니다.

# 사용할 LLM 모델 ID 설정
model_id = 'spow12/Ko-Qwen2-7B-Instruct'

# 토크나이저 로드 : 해당 모델을 최초로 다운로드 했을 때는 진행바가 나타남. 
tokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)
# tokenizer = AutoTokenizer.from_pretrained(model_id)
print("tokenizer finish")

 

"달콤쌉쌀한 맛이예요. 먹다보면 익숙해져서 맛있어요"라는 문장을 예시로 토큰화 및 디코딩을 수행하면 다음과 같이 출력됩니다.

 

Pipeline

  • pipeline은 학습된 모델을 간단하고 빠르게 사용할 수 있도록 하는 클래스로, 모델과 태스크를 입력하거나, 혹은 특정 태스크로 학습된 모델을 입력하면 전처리부터 결과를 내는 모든 과정을 한 번에 처리해주는 강력한 기능이라고 합니다. 
# 모델을 평가 모드로 설정 (추론 시 필요)
model.eval()

# 대화형 태스크를 위한 파이프라인 생성
pipe = pipeline("text-generation", model=model, tokenizer=tokenizer, device_map='auto')

# 스트리머 생성 (텍스트를 실시간으로 출력하기 위함)
streamer = TextStreamer(tokenizer)

# 텍스트 생성 설정
generation_configs = dict(
    max_new_tokens=2048,  # 최대 생성할 토큰 수
    num_return_sequences=1,  # 생성할 응답 개수
    temperature=0.1,  # 낮은 온도로 일관성 있는 답변 생성
    repetition_penalty=1.2,  # 반복 패턴을 줄이기 위한 패널티 적용
    num_beams=1,  # 빔 서치 사용 여부 (1이면 사용 안 함)
    do_sample=True,  # 샘플링 사용 여부
    top_k=20,  # top-k 샘플링 사용 (상위 20개 토큰 중 선택)
    top_p=0.9,  # nucleus 샘플링 사용 (확률 합이 0.9 이상인 토큰 선택)
    eos_token_id=tokenizer.eos_token_id,  # 종료 토큰 설정
    pad_token_id=tokenizer.eos_token_id,  # 패딩 토큰 설정 (EOS와 동일하게 처리)
    streamer=streamer  # 스트리밍 출력 사용
)

# 시스템 역할 메시지 설정 (모델의 기본 대화 스타일 정의)
sys_message = """당신은 친절한 챗봇으로서 상대방의 요청에 최대한 자세하고 친절하게 답해야합니다. 
사용자가 제공하는 정보를 세심하게 분석하여 사용자의 의도를 신속하게 파악하고 그에 따라 답변을 생성해야합니다.  
항상 매우 자연스러운 한국어로 응답하세요."""

# 대화 이력 설정 (시스템 메시지 + 사용자 입력)
message = [
    {
        'role': "system",
        'content': sys_message  # 챗봇의 역할과 스타일을 정의하는 시스템 메시지
    },
    {
        'role': 'user',
        'content': "현재의 경제상황에 대해 어떻게 생각해?."  # 사용자의 입력
    }
]

# 모델을 사용하여 대화 생성
conversation = pipe(message, **generation_configs)

 

위의 코드를 실행하고 응답을 출력해 답변을 확인할 수 있습니다. 

'현재의 경제 상황에 대해 어떻게 생각하냐'는 질문을 던졌더니 본인은 인공지능이라며 애매모호한 답변을 하네요! 아쉽긴 하지만.. 질문 자체도 모호하기도 했고 지금 단계에서는 퀄리티보다는 우선 답변이 나오는 것에 포커스를 두겠습니다.

 

⭐️ Tip

* 실행 환경에 따라 다음과 같이 세션이 다운되거나 답변속도가 매우 느린 현상이 발생할 때가 발생할 수 있습니다

 

이럴 때는 모델 경량화 방법 중 하나인 양자화(quantization)를 진행해주면 됩니다 😉

💡 양자화
- 양자화란 모델의 파라미터, 가중치를 더 작은 비트를 사용하는 데이터타입으로 나타내는 것을 의미합니다.
- 해당 방법을 사용하면 모델이 메모리상 차지하는 용량을 줄일 수 있고 연산속도도 빨라진다는 장점이 있지만 정보손실이 일어나 성능이 감소하는 단점이 있습니다.

 

1. bitsandbytes 라이브러리를 install 해주고

! pip install bitsandbytes

 

2. 다음의 코드를 활용해보세요

import pandas as pd
import torch
# BitsAndBytesConfig 클래스 추가 호출 
from transformers import (AutoTokenizer, AutoModelForCausalLM, pipeline, BitsAndBytesConfig)
from transformers.generation.utils import GenerationConfig
from transformers import TextStreamer

# 사용할 LLM 모델 ID 설정
model_id = 'spow12/Ko-Qwen2-7B-Instruct'

# 토크나이저 로드 : 해당 모델을 최초로 다운로드 했을 때는 진행바가 나타남. 
tokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)
# tokenizer = AutoTokenizer.from_pretrained(model_id)
print("tokenizer finish")

# 양자화 
bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_use_double_quant=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.bfloat16,
)


# 모델 로드
model = AutoModelForCausalLM.from_pretrained(
    model_id,
    torch_dtype=torch.float32,
    device_map='auto' # GPU 자동 할당
    ,quantization_config=bnb_config  # 양자화
)
print("model load finish")


# 모델을 평가 모드로 설정 (추론 시 필요)
model.eval()

# 대화형 태스크를 위한 파이프라인 생성
pipe = pipeline("text-generation", model=model, tokenizer=tokenizer, device_map='auto')

# 스트리머 생성 (텍스트를 실시간으로 출력하기 위함)
streamer = TextStreamer(tokenizer)

# 텍스트 생성 설정
generation_configs = dict(
    max_new_tokens=2048,  # 최대 생성할 토큰 수
    num_return_sequences=1,  # 생성할 응답 개수
    temperature=0.1,  # 낮은 온도로 일관성 있는 답변 생성
    repetition_penalty=1.2,  # 반복 패턴을 줄이기 위한 패널티 적용
    num_beams=1,  # 빔 서치 사용 여부 (1이면 사용 안 함)
    do_sample=True,  # 샘플링 사용 여부
    top_k=20,  # top-k 샘플링 사용 (상위 20개 토큰 중 선택)
    top_p=0.9,  # nucleus 샘플링 사용 (확률 합이 0.9 이상인 토큰 선택)
    eos_token_id=tokenizer.eos_token_id,  # 종료 토큰 설정
    pad_token_id=tokenizer.eos_token_id,  # 패딩 토큰 설정 (EOS와 동일하게 처리)
    streamer=streamer  # 스트리밍 출력 사용
)

# 시스템 역할 메시지 설정 (모델의 기본 대화 스타일 정의)
sys_message = """당신은 친절한 챗봇으로서 상대방의 요청에 최대한 자세하고 친절하게 답해야합니다. 
사용자가 제공하는 정보를 세심하게 분석하여 사용자의 의도를 신속하게 파악하고 그에 따라 답변을 생성해야합니다.  
항상 매우 자연스러운 한국어로 응답하세요."""

# 대화 이력 설정 (시스템 메시지 + 사용자 입력)
message = [
    {
        'role': "system",
        'content': sys_message  # 챗봇의 역할과 스타일을 정의하는 시스템 메시지
    },
    {
        'role': 'user',
        'content': "현재의 경제상황에 대해 어떻게 생각해?."  # 사용자의 입력
    }
]

# 모델을 사용하여 대화 생성
conversation = pipe(message, **generation_configs)

Part2. 요약 태스크 수행 모델

 이번에는 모델과 양자화 방식은 동일하되, sentence의 양을 좀 더 늘려서 요약 태스크를 수행하도록 만들어보겠습니다.

model_name = 'spow12/Ko-Qwen2-7B-Instruct'
tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)

# Model
bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_use_double_quant=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.bfloat16,
)

model = AutoModelForCausalLM.from_pretrained(
    model_name,
    torch_dtype=torch.float32,
    # attn_implementation="flash_attention_2",
    device_map='auto',
    quantization_config=bnb_config
)

model.eval()

pipe = pipeline(
    "text-generation",
    model=model,
    tokenizer=tokenizer,
    max_new_tokens=512,
    config=model.generation_config  # 기본 generation_config 설정
)


# 패딩이 종료 토큰처럼 처리되어 모델이 불필요하게 텍스트를 생성하는 것을 방지하기 위한 설정
pipe.model.config.pad_token_id = pipe.model.config.eos_token_id

# data load
sentences = """
다크 초콜릿의 쓴맛이 정말 고급스럽고 깊어요. 살짝 달콤한 끝맛 덕분에 너무 자극적이지 않아서 자주 먹을 수 있을 것 같아요. 카카오 함량이 높아도 씁쓸한 맛이 오래 남지 않아서 좋았어요.
다크 초콜릿을 좋아하는데, 이 제품은 정말 고소한 맛이 강해요. 통밀로 만든 칩이라 그런지 씹을 때마다 고소함이 확 느껴져서 만족스러웠습니다. 너무 단 초콜릿을 싫어하는 사람에게 완벽해요.
초콜릿의 맛은 정말 훌륭하지만, 포장에 성분이 조금 더 잘 보였으면 좋겠어요. 건강을 신경 쓰는 사람들에게는 성분이 바로 보이는 게 중요하니까요.
이 다크 초콜릿은 그 어떤 다크 초콜릿보다 맛있어요. 끝 맛이 깔끔하고, 너무 달지도 않고 너무 쓰지도 않아서 누구나 좋아할 만한 균형 잡힌 맛입니다. 커피와 함께 먹으면 정말 최고의 조합이에요!
다크 초콜릿의 쓴맛이 너무 강하지 않아서 초콜릿 특유의 맛을 제대로 즐길 수 있어요. 맛이 깔끔하고 부드러워서 간식으로 먹기 좋고, 성분도 꽤 신경 써서 만든 느낌이라 더욱 믿음이 가네요.
카카오 함량이 높다고 해서 너무 쓴 초콜릿일까 걱정했는데, 예상보다 맛있었어요. 쓴맛과 단맛이 잘 어우러져서 맛의 균형이 정말 좋습니다. 초콜릿을 좋아하는 사람이라면 정말 추천하고 싶어요.
다크 초콜릿 특유의 쓴맛이 입안을 깔끔하게 씻어주면서, 끝맛은 부드럽게 넘어가네요. 고소한 맛이 강해서 하루의 스트레스를 풀기에 딱 좋습니다. 이 초콜릿으로 휴식 시간을 즐기세요.
초콜릿 이름이 너무 길고 영어가 커서 처음에 정확히 알아보기 힘들었어요. 간단한 이름으로 바뀌면 더 좋을 것 같네요. 그래도 맛은 정말 훌륭하고, 한 번 먹으면 계속 손이 가요.
카카오 함량이 높아서 그런지 초콜릿이 좀 더 고급스러운 맛이에요. 맛이 진하면서도 달콤한 끝맛이 남아서 자꾸 먹게 되네요. 초콜릿을 자주 먹는 사람에게 강추합니다!
"""

answer = pipe(f"[역할]\n다음 [요약 대상]의 내용에 대해 3문장으로 요약해주세요. 한국어로 답변하세요. \n[요약 대상]\n{sentences}\n")
print(f"요약 글 : {answer[0]['generated_text']}")

 

output은 다음과 같이 출력됩니다. (아까와는 다르게  pipe부분에 str으로 넣었는데, 방식은 다양합니다.)

원하는 방향으로 프롬프트를 커스텀 or sentences를 변경하여 응용도 가능하겠죠

 

 

지금까지 transformers 라이브러리의 주요 클래스, 요약 태스크 생성모델 구현 방법, 경량화 방법에 대해 다루어보았습니다. 

라이브러리가 추상화가 잘되어있어서 생각보다 구현 방법은 그렇게 복잡하지 않았지만, 클래스를 활용하는 방법의 경우 처음 보는 상황이라면 다소 헷갈릴 수 있기에 학습의 과정이 필요합니다. 도움이 되셨기를 바라며, 이만 글을 마무리하겠습니다 🐰🍀

 


+ 학습&글 작성에 도움을 받은 책 : 자연어 처리를 위한 허깅페이스 트랜스포머 하드트레이닝 (박성환, 남승우)