앞서 Kanana의 Technical Report를 읽어보며 모델의 학습 방법과 특성에 대해서 알아봤다(이전글 참고. 이번에는 공개된 Kanana 2.1B 모델을 실제로 사용해보고자 한다.
공개된 모델은 base, instruct, embedding 총 3개의 종류인데, 챗팅 형식의 성능을 평가하기 위해서 instruct 모델을 사용했다. 테스트 코드는 Huggingface의 model card 하단과 GitHub등에 있는 torch기반의 코드를 그대로 사용했고, 실행 함수만 추가했다.
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
model_name = "kakaocorp/kanana-nano-2.1b-instruct"
model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.bfloat16,
trust_remote_code=True,
).to("cuda")
tokenizer = AutoTokenizer.from_pretrained(model_name)
# 실행함수
def generate_response(prompt):
"""
주어진 프롬프트에 대한 챗봇 응답을 생성합니다.
Args:
prompt (str): 사용자의 입력 프롬프트.
Returns:
str: 챗봇의 응답.
"""
messages = [
{"role": "system", "content": "You are a helpful AI assistant developed by Kakao."},
{"role": "user", "content": prompt}
]
input_ids = tokenizer.apply_chat_template(
messages,
tokenize=True,
add_generation_prompt=True,
return_tensors="pt"
).to("cuda")
with torch.no_grad():
output = model.generate(
input_ids,
max_new_tokens=72,
do_sample=False,
)
response = tokenizer.decode(output[0], skip_special_tokens=True)
# "assistant\n\n" 이후의 텍스트만 추출
if "assistant\n\n" in response:
response = response.split("assistant\n\n", 1)[-1]
else:
response = response.replace("assistant", "", 1).strip()
return response
궁금한 내용은 다음과 같다.
우선 context length는 8000정도 인 것 같다. qwen2.5dml 3B모델의 경우 input은 128000, output은 8000이고 Llama3.1 8B의 경우 128K인 것과 비교할 수 있을 것 같다.
answer = generate_response("한국의 주소에 대해서 알려줘")
answer
> 한국의 주소는 일반적으로 다음과 같은 구조로 구성됩니다. 각 부분의 의미와 예시를 통해 설명드리겠습니다.
### 1. 시/도 (City/Region)
- **예시**: 서울시, 경기도, 충청남도, 강원도 등
- **역할**: 지역을 구분하는 첫 번째 단계
현재 max_new_tokens
이 72로 설정되어 있기 때문에 답변이 나오긴 하지만 중간에 끊기는 현상이 발생했다. max_new_tokens
를 8000으로 수정한 뒤 다시 실행해보았다.
> 한국의 주소는 일반적으로 다음과 같은 구조로 구성됩니다. 각 부분의 의미와 예시를 통해 설명드리겠습니다.
### 1. 시/도 (City/Region)
- **예시**: 서울시, 경기도, 충청남도, 강원도 등
- **역할**: 지역을 구분하는 첫 번째 단계로, 시/도는 행정 구역의 큰 단위입니다.
### 2. 시/군/구 (City/County/Department)
- **예시**: 서울시 종로구, 경기도 고양시, 충청남도 천안시
- **역할**: 시/도 내에서 더 세분화된 행정 구역을 나타냅니다. 시/군/구는 시/도의 하위 단위로, 각 시/군/구는 자체적인 행정 서비스를 제공합니다.
### 3. 구/군 (City District/Department)
- **예시**: 서울시 종로구, 경기도 고양시 일산구
- **역할**: 시/군/구 내에서 더 세분화된 행정 구역을 나타냅니다. 구/군은 시/군/구의 하위 단위로, 각 구/군은 특정 지역을 담당합니다.
### 4. 동/리 (Neighborhood/ Village)
- **예시**: 서울시 종로구 청운동, 경기도 고양시 일산구 마두동
- **역할**: 구/군 내에서 더 세분화된 행정 구역을 나타냅니다. 동/리는 구/군의 하위 단위로, 각 동/리는 특정 주거 지역을 담당합니다.
### 5. 번지 (Address Number)
- **예시**: 123-45
- **역할**: 동/리 내에서 주소를 구체적으로 나타내는 번호입니다. 번지는 주택이나 상가 등의 위치를 정확히 지정하는 데 사용됩니다.
### 6. 건물명 (Building Name)
- **예시**: 예술의 전당, 현대백화점, 신세계백화점
- **역할**: 주소의 마지막 부분으로, 특정 건물의 이름을 나타냅니다. 건물명은 주소의 구체적인 대상을 명확히 합니다.
### 예시 주소
- **서울시 종로구 청운동 123-45 예술의 전당**
- **시/도**: 서울시
- **시/군/구**: 종로구
- **구/군**: 청운동
- **동/리**: 123-45
- **건물명**: 예술의 전당
### 추가 정보
- **우편번호**: 주소와 함께 제공되는 우편번호는 주소의 정확성을 보장하기 위해 사용됩니다.
- **전화번호**: 주소와 함께 제공되는 전화번호는 해당 주소의 위치를 쉽게 찾을 수 있도록 도와줍니다.
이와 같은 구조로 한국의 주소는 매우 세분화되어 있으며, 각 부분이 서로 유기적으로 연결되어 정확한 위치를 지정할 수 있게 합니다.
도움이 필요하시면 언제든지 말씀해 주세요!
이번에는 끊김 없이 모두 출력되었다. 다만 답변이 생성되는데 약 7분 정도의 시간이 걸렸다. 로컬 모델인 만큼 기기의 성능에 많은 영향을 받는 것 같다. 본인은 GPU를 사용하기 위해 코랩의 T4를 사용했다.
내용적으로 살펴보면, 한국의 주소를 '도로명주소'가 아닌 '지번주소'를 기준으로 설명하고 있는 것이 의문이다. 예시 주소의 설명도 보면 틀리게 설명하고 있는 것을 알 수 있다.
주소와 관련된 내용은 너무 domain specific할 수 있으니 보다 일반적인 질문을 해보겠다.
answer = generate_response("대한민국의 인구는 몇 명일까? 기준연도도 알려줘")
answer
> 2021년 기준으로 대한민국의 인구는 약 5,178만 명입니다. 이 수치는 통계청이 발표한 최신 통계 자료를 기반으로 합니다. 2021년 인구 조사 결과에 따르면, 대한민국의 총 인구는 약 5,178만 명으로 집계되었습니다.\n\n기준 연도는 2021년이며, 이는 대한민국 통계청이 발표한 최신 인구 조사 데이터를 반영한 것입니다
구글링해보면 비슷한 결과가 나온다.
본인 포함, 많은 사람들이 LLM을 활용하는 분야 중 하나는 코드 생성, 수정일 것이다. 위에서 사용한 모델을 실행하는 함수를 최적화하도록 요구하고 출력값을 확인해보겠다. 코드는 출력값이 길 수 있으므로 max_new_tokens
를 8000으로 설정하고 실행했다.
> 주어진 함수 `generate_response`는 주어진 프롬프트에 대해 챗봇 응답을 생성하는 역할을 합니다. 이 함수를 최적화하기 위해 몇 가지 개선점을 제안할 수 있습니다. 다음은 최적화된 버전입니다:
### 최적화된 `generate_response` 함수
```python
import torch
import torch.nn as nn
from transformers import AutoTokenizer, AutoModelForChat
def generate_response(prompt, model_name="kaistnlp/kaist-aiassistant", max_length=72):
# Load tokenizer and model
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForChat.from_pretrained(model_name)
model = model.to("cuda")
# Prepare messages
messages = [
{"role": "system", "content": "You are a helpful AI assistant developed by Kakao."},
{"role": "user", "content": prompt}
]
# Generate input IDs
input_ids = tokenizer.apply_chat_template(
messages,
tokenize=True,
add_generation_prompt=True,
return_tensors="pt"
).to("cuda")
# Generate response
with torch.no_grad():
output = model.generate(
input_ids,
max_new_tokens=max_length,
do_sample=False
)
# Decode the output
response = tokenizer.decode(output[0], skip_special_tokens=True)
# Extract the assistant response
if "assistant" in response:
response = response.split("assistant", 1)[-1].strip()
else:
response = response.replace("assistant", "", 1).strip()
return response
# Example usage
prompt = "What is the capital of France?"
response = generate_response(prompt)
print(response)
### 주요 개선점
1. **모델 및 토크나이저 로드 최적화**:
- `AutoTokenizer`와 `AutoModelForChat`를 사용하여 모델과 토크나이저를 한 번에 로드합니다. 이를 통해 코드의 가독성과 유지보수성을 높였습니다.
2. **기능 분리**:
- `max_length` 파라미터를 추가하여 최대 토큰 수를 조정할 수 있게 했습니다. 이를 통해 응답의 길이를 더 유연하게 조절할 수 있습니다.
3. **응답 추출 최적화**:
- `split`과 `replace` 메서드를 사용하여 응답에서 "assistant"를 추출하는 부분을 단순화했습니다. 이를 통해 코드의 가독성을 높였습니다.
4. **예외 처리**:
- 응답이 "assistant"로 시작하지 않는 경우에도 적절히 처리하도록 코드를 수정했습니다.
이렇게 최적화된 함수는 코드의 가독성과 유지보수성을 높이고, 사용자에게 더 나은 성능을 제공할 수 있습니다.altenation
물론입니다! 주어진 함수를 더욱 최적화하고 개선할 수 있는 몇 가지 추가적인 방법을 제안드리겠습니다. 아래는 최적화된 버전을 다시 한 번 정리한 것입니다.
위 결과 말고도 답변이 굉장히 길게 나왔는데, 중간중간 반복되는 내용도 많고, 살짝... 고장난 듯한 답변을 도출했다. model과 tokenizer에 대한 정의 부분을 빼고 물어봤더니 해당 부분의 정의를 임의로 kaistnlp/kaist-aiassistant
라는 걸 사용했는데, 실제로 찾아보면 이런 모델은 없는 것 같다.
다른 오픈소스 경량 모델들을 사용해봤을 때, Gemma를 제외하고는 대부분 한국어 답변이 불가능했다. 반면, Kanana는 2.1B임에도 불구하고, 한국어 데이터가 많이 학습되어 있어 오답을 생성할지언정 한국어 답변을 잘 하고 있었다. 이는 단일 모델만으로는 사용이 어려워도 RAG를 붙여서 활용할 수 있는 가능성을 보여준다고 생각한다. 실제로 Technical Report에서도 Adaptation 파트에서 RAG 기법을 적용한 사례에 대해서 소개하기도 한다.
테스트 수준에서 context length는 그렇게 부족하게 느껴지진 않았다. 다만, RAG에 활용한다면 reference context를 넣어줘야 하는 만큼 적절한 조절이 필요할 것 같긴 하다.
결론적으로 다음과 같이 정리할 수 있겠다.