3장. Attention 과 Transformer, GPT 직접 구현
실습 6 — 작은 챗봇 모델 써 보기
디코더만 쓰는 작은 대화 모델 Qwen/Qwen2.5-0.5B-Instruct(파라미터 약 4억 9,400만 개, Apache 2.0 라이선스)에 고객 문의를 넣어 답을 받아 봅니다. 대화 모델이 받는 입력의 꼴(채팅 템플릿)을 직접 보고, 시스템 메시지를 바꾸면 답이 어떻게 달라지는지 봅니다. 처음 실행할 때 1GB 쯤을 내려받습니다. 결과를 같게 맞추려고 CPU 에서 탐욕적 선택(늘 1등 토큰)으로 답하게 하며, 답 하나에 수 초씩 걸립니다.
1단계. 모델 불러오기
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM
name = "Qwen/Qwen2.5-0.5B-Instruct"
tokenizer = AutoTokenizer.from_pretrained(name)
# CausalLM: 앞만 보고 다음 토큰을 맞히는(디코더만 쓰는) 모델. float32 로 CPU 에서 돌린다
model = AutoModelForCausalLM.from_pretrained(name, dtype=torch.float32)
model.eval()
print("파라미터 수:", sum(p.numel() for p in model.parameters()))파라미터 수: 494032768이 단계의 함수입니다.
| 코드 | 쉬운 뜻 |
|---|---|
AutoModelForCausalLM | 디코더만 쓰는 모델을 불러오는 도구이다. CausalLM 은 앞의 토큰만 보고 다음 토큰을 맞히는 언어 모델이다 |
from_pretrained(name, dtype=torch.float32) | 가중치를 32비트 실수로 불러온다. CPU 는 16비트 계산이 느리거나 안 되므로 float32 를 준다 |
model.eval() | 실습 5와 같이 평가 상태로 둔다 |
sum(p.numel() for p in model.parameters()) | 실습 5와 같은 파라미터 세기이다. 출력은 494032768 이다 |
2단계. 채팅 템플릿 보기
# 대화는 역할(role)과 내용(content)을 가진 메시지의 목록이다
messages = [
{"role": "system", "content": "당신은 쇼핑몰 고객센터 상담원입니다."},
{"role": "user", "content": "배송이 늦어요."},
]
# 모델이 학습 때 본 꼴의 글로 바꾼다. add_generation_prompt=True 는 "이제 상담원 차례"라는 표시를 붙인다
prompt = tokenizer.apply_chat_template(messages, add_generation_prompt=True, tokenize=False)
print(prompt)<|im_start|>system
당신은 쇼핑몰 고객센터 상담원입니다.<|im_end|>
<|im_start|>user
배송이 늦어요.<|im_end|>
<|im_start|>assistant이 단계의 함수입니다.
| 코드 | 쉬운 뜻 |
|---|---|
{"role": "system", "content": ...} | 메시지 하나이다. role 은 누가 말했는지(system 은 모델에게 주는 지시, user 는 고객, assistant 는 모델), content 는 말의 내용이다 |
tokenizer.apply_chat_template(messages, ...) | 메시지 목록을 이 모델이 학습 때 본 꼴의 글로 바꾼다. 역할마다 시작과 끝을 알리는 특별한 표시(im_start, im_end)가 붙는다 |
add_generation_prompt=True | 끝에 im_start 표시와 assistant 를 붙인다. 모델이 이어 쓸 차례가 상담원(assistant)이라는 표시이다 |
tokenize=False | 번호로 바꾸지 않고 글 그대로 돌려준다. 꼴을 눈으로 보려고 쓴다 |
3단계. 답 받기
def ask(system, question, max_new_tokens=80):
"""시스템 메시지와 질문을 넣고 모델의 답만 돌려준다."""
messages = [{"role": "system", "content": system}, {"role": "user", "content": question}]
enc = tokenizer.apply_chat_template(messages, add_generation_prompt=True,
return_tensors="pt", return_dict=True)
with torch.no_grad():
# do_sample=False: 확률대로 뽑지 않고 늘 1등 토큰을 고른다(탐욕적 선택). 다시 돌려도 같은 답
out = model.generate(**enc, max_new_tokens=max_new_tokens, do_sample=False)
new_tokens = out[0][enc["input_ids"].shape[1]:] # 입력 뒤에 새로 생긴 토큰만
return tokenizer.decode(new_tokens, skip_special_tokens=True).strip()
system = "당신은 온라인 쇼핑몰 고객센터 상담원입니다. 한국어로 두 문장 안에서 짧고 친절하게 답합니다."
for q in ["주문한 지 일주일이 지났는데 아직 안 왔어요.", "환불은 며칠 걸려요?"]:
print("고객:", q)
print("모델:", ask(system, q))고객: 주문한 지 일주일이 지났는데 아직 안 왔어요.
모델: 죄송합니다, 저는 인공지능으로서 주문을 처리하거나 상황에 따라 대응하는 능력이 없습니다. 하지만 고객님의 질문에 대해 도움을 드릴 수 있습니다. 주문 상태를 확인해주시면 감사하겠습니다.
고객: 환불은 며칠 걸려요?
모델: 환불 시간은 상황에 따라 달라집니다. 일반적으로 3-7일 정도가 빠르게 처리될 수 있습니다. 하지만, 예상치 못한 경우는 언제든 연락주시기 바랍니다.이 단계의 함수입니다.
| 코드 | 쉬운 뜻 |
|---|---|
apply_chat_template(..., return_tensors="pt", return_dict=True) | 템플릿 글을 바로 토큰 번호로 바꾸고 input_ids 와 attention_mask 가 든 사전 꼴로 돌려준다. 텐서는 PyTorch 꼴이다 |
model.generate(**enc, ...) | 사전을 풀어 넘기고, 입력 뒤에 새 토큰을 하나씩 붙여 나간다 |
max_new_tokens=max_new_tokens | 새로 만드는 토큰의 수를 80개까지로 막는다. 넘으면 답 중간에서 끊긴다 |
do_sample=False | 확률대로 뽑지 않고 늘 1등 토큰을 고른다(탐욕적 선택). 다시 돌려도 같은 답이 나온다 |
out[0] | 배치의 첫 줄이다. 입력 토큰과 새 토큰이 이어 붙어 있다 |
enc["input_ids"].shape[1] | 입력 토큰의 개수이다. 이 자리부터 자르면 out[0][...:] 에 새로 만든 답 토큰만 남는다 |
tokenizer.decode(new_tokens, skip_special_tokens=True) | 번호 줄 하나를 글로 되돌린다. 끝 표시(im_end) 같은 특별한 토큰은 뺀다 |
generate 는 질문까지 포함한 전체 토큰을 돌려줍니다. 입력 길이만큼 앞을 잘라 내야 모델의 답만 남습니다.
4단계. 시스템 메시지 바꿔 보기
# 같은 질문에 말투만 바꾼 시스템 메시지를 준다
playful = "당신은 반말로 장난스럽게 말하는 쇼핑몰 마스코트입니다. 한 문장으로 답합니다."
q = "주문한 지 일주일이 지났는데 아직 안 왔어요."
print("시스템 메시지를 바꾼 답:", ask(playful, q))시스템 메시지를 바꾼 답: 죄송합니다, 저는 실시간 정보를 제공하지 못하고 있습니다. 주문 상태에 대한 최신 정보를 확인해주시면 감사하겠습니다.이 단계의 함수는 3단계의 표와 같습니다. 같은 ask 에 시스템 메시지만 바꿔 넘겼습니다.
이 실습이 뜻하는 것
이 실습은 이미 대화를 배운 작은 모델에게 고객 응대를 맡겨 봅니다. 첫 출근한 아르바이트생에 빗대 봅니다. 아르바이트생을 뽑고(1단계), 그가 익숙한 업무 지시서 양식을 펼쳐 보고(2단계), 손님 문의 두 개에 답하게 한 뒤(3단계), 지시서의 말투 부분만 바꿔 같은 문의에 다시 답하게 합니다(4단계). 여기서 아르바이트생이 대화 모델, 업무 지시서 양식이 채팅 템플릿, 지시서에 적는 "이렇게 행동하라"는 말이 시스템 메시지입니다. 사람 아르바이트생은 모르면 물어보지만, 이 모델은 모르는 것도 그럴듯하게 지어서 말합니다.
| 단계 | 출력 | 뜻 |
|---|---|---|
| 1. 파라미터 수 | 494032768 | 숫자 약 4억 9,400만 개로 된 모델입니다. 실습 5의 번역 모델의 약 6.3배, 미니 GPT 의 약 4,100배입니다 |
| 2. 채팅 템플릿 | im_start 와 system … im_start 와 assistant | 우리가 준 메시지 둘이 역할 표시가 붙은 글 한 덩어리로 바뀌었습니다. 맨 끝의 assistant 는 "이제 상담원이 말할 차례"라는 표시이고, 모델은 그 뒤를 이어 씁니다 |
| 3. 배송 문의 | "저는 인공지능으로서 주문을 처리하거나 … 능력이 없습니다" | 상담원 역할을 맡겼는데도 자기를 인공지능이라고 밝히며 비켜 갔습니다. "두 문장 안에서"라는 지시도 넘겨 세 문장으로 답했습니다 |
| 3. 환불 문의 | "일반적으로 3-7일 정도" | 이 쇼핑몰의 환불 정책을 알려 준 적이 없는데 기간을 지어냈습니다. 이렇게 그럴듯하게 지어낸 답을 환각이라고 합니다 |
| 4. 말투 바꾸기 | "죄송합니다, 저는 실시간 정보를 제공하지 못하고 있습니다…" | 반말로 장난스럽게, 한 문장으로 답하라고 했는데 존댓말로 두 문장을 답했습니다. 작은 모델은 시스템 메시지를 따르는 힘이 약합니다 |
한 줄로 줄이면, 대화 모델은 채팅 템플릿의 꼴로 입력을 받아 답을 이어 쓰지만, 0.5B 크기의 작은 모델은 우리 정책을 모르고 지시도 잘 따르지 않습니다. 프로젝트 1에서는 FAQ 를 함께 넣어 이 한계를 얼마나 줄일 수 있는지 봅니다. 답이 실행마다 같은 것은 탐욕적 선택(do_sample=False)을 썼기 때문이고, 그 답이 맞다는 뜻은 아닙니다. 질문 두 개로 본 결과라서 이 모델의 실력 전체를 말하지도 않습니다.
코드 설명
apply_chat_template은 메시지 목록을 모델이 학습 때 본 특별한 표시(<|im_start|>같은 것)가 붙은 글로 바꿔 줍니다. 대화 모델마다 이 꼴이 다르므로 손으로 만들지 않고 이 함수를 씁니다.- 파라미터는 494,032,768개(약 4억 9,400만)입니다. 채팅 템플릿 출력에서
<|im_start|>system,<|im_start|>user,<|im_start|>assistant가 역할을 나누는 표시이고, 모델은 마지막assistant뒤를 이어 씁니다. - 첫 질문에는 "저는 인공지능으로서 주문을 처리하거나 … 능력이 없습니다"라고 답했고, 환불 기간은 "일반적으로 3-7일"이라고 지어냈습니다. 이 쇼핑몰의 정책을 모르니 그럴듯한 말을 만들어 낸 것입니다(환각).
- 반말로 장난스럽게 말하라고 했는데도 "죄송합니다, 저는 실시간 정보를 제공하지 못하고 있습니다"처럼 존댓말로 답했습니다. 0.5B 모델은 시스템 메시지의 지시를 따르는 힘이 약합니다. 큰 모델일수록 이런 지시를 잘 따릅니다.
- 0.5B 는 파라미터 5억 개라는 뜻입니다. 요즘 큰 LLM 의 수백분의 일 크기라 우리 PC 에서도 돌지만, 그만큼 아는 것과 지시를 따르는 힘이 약합니다. 프로젝트에서 이 한계를 직접 봅니다.