LLM 엔지니어링3장 · Attention 과 Transformer, GPT 직접 구현20 / 23← 이전목차다음 →MetaContents

3장. Attention 과 Transformer, GPT 직접 구현

실습 6 — 작은 챗봇 모델 써 보기

디코더만 쓰는 작은 대화 모델 Qwen/Qwen2.5-0.5B-Instruct(파라미터 약 4억 9,400만 개, Apache 2.0 라이선스)에 고객 문의를 넣어 답을 받아 봅니다. 대화 모델이 받는 입력의 꼴(채팅 템플릿)을 직접 보고, 시스템 메시지를 바꾸면 답이 어떻게 달라지는지 봅니다. 처음 실행할 때 1GB 쯤을 내려받습니다. 결과를 같게 맞추려고 CPU 에서 탐욕적 선택(늘 1등 토큰)으로 답하게 하며, 답 하나에 수 초씩 걸립니다.

1단계. 모델 불러오기

Python
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단계. 채팅 템플릿 보기

Python
# 대화는 역할(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단계. 답 받기

Python
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단계. 시스템 메시지 바꿔 보기

Python
# 같은 질문에 말투만 바꾼 시스템 메시지를 준다
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 에서도 돌지만, 그만큼 아는 것과 지시를 따르는 힘이 약합니다. 프로젝트에서 이 한계를 직접 봅니다.
← 이전 절3장 · 실습 5 — 번역 모델 써 보기다음 절 →3장 · 프로젝트 1 — 고객 문의 챗봇 맛보기