A Rank Stabilization Scaling Factor for Fine-Tuning with LoRA
Paper • 2312.03732 • Published • 12
운세 전문 한국어 LLM - Qwen3-4B 기반 rsLoRA Fine-tuning (ChatML 직접 구현)
Yeji-4B-rsLoRA-v8.1은 동양 및 서양 운세 생성에 특화된 한국어 언어 모델입니다. Qwen3-4B-Instruct를 기반으로 rsLoRA 방식으로 파인튜닝되었으며, Unsloth의 ChatML 버그를 우회하기 위해 직접 ChatML 포맷을 구현한 것이 특징입니다.
<think> 태그 완전 제거: 직접 ChatML 포맷 구현으로 Unsloth 버그 해결| 속성 | 값 |
|---|---|
| 베이스 모델 | Qwen/Qwen3-4B-Instruct-2507 |
| 파인튜닝 방식 | rsLoRA (r=64, alpha=128, dropout=0.05) |
| 학습 데이터 | 33,528건 (동양 22,022 + 서양 11,506) |
| 학습 에폭 | 5 에폭 |
| 컨텍스트 길이 | 4096 토큰 |
| 모델 크기 | ~8GB |
| 라이선스 | Apache-2.0 |
| 언어 | 한국어 |
rsLoRA는 LoRA의 개선 버전으로, rank-stabilized 방식을 통해 학습 안정성을 높입니다:
LoRA: ΔW = BA
rsLoRA: ΔW = (B * α/√r) * A
# Python 3.11+ 권장
pip install vllm>=0.13.0 # vLLM 사용 시
# 또는
pip install transformers>=4.50.0 torch>=2.6.0
vllm serve tellang/yeji-4b-rslora-v8.1 \
--host 0.0.0.0 \
--port 8001 \
--dtype auto \
--max-model-len 4096 \
--gpu-memory-utilization 0.9
OpenAI 호환 API 호출:
import openai
client = openai.OpenAI(
base_url="http://localhost:8001/v1",
api_key="EMPTY",
)
completion = client.chat.completions.create(
model="tellang/yeji-4b-rslora-v8.1",
messages=[
{"role": "system", "content": "당신은 운세를 알려주는 AI입니다."},
{"role": "user", "content": "오늘의 운세를 알려주세요."}
],
temperature=0.7,
max_tokens=2048,
)
print(completion.choices[0].message.content)
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
model_id = "tellang/yeji-4b-rslora-v8.1"
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(
model_id,
torch_dtype=torch.bfloat16,
device_map="auto",
)
messages = [
{"role": "system", "content": "당신은 운세를 알려주는 AI입니다."},
{"role": "user", "content": "오늘의 연애운을 알려주세요."}
]
text = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True,
)
inputs = tokenizer([text], return_tensors="pt").to(model.device)
outputs = model.generate(
**inputs,
max_new_tokens=2048,
temperature=0.7,
do_sample=True,
)
response = tokenizer.decode(outputs[0], skip_special_tokens=True)
print(response)
system_prompt = """당신은 운세 전문가입니다.
사용자의 질문에 대해 JSON 형식으로 응답하세요.
출력 형식:
{
"overall_summary": "전체 요약",
"사주분석": {...},
"타로": {...},
"주역": {...},
"성좌": {...},
"keywords": ["키워드1", "키워드2"],
"lucky_items": ["행운아이템1"],
"caution": "주의사항"
}
"""
user_prompt = """
사용자 정보:
- 이름: 홍길동
- 생년월일: 1990-01-15
- 성별: 남
- 태어난 시간: 14시
운세 유형: 종합운세
"""
system_prompt = """당신은 서양 점성술 전문가입니다.
JSON 형식으로 응답하세요.
출력 형식:
{
"overall_summary": "전체 요약",
"사랑운": {"score": 85, "description": "..."},
"직업운": {"score": 70, "description": "..."},
"건강운": {"score": 90, "description": "..."},
"돈운": {"score": 75, "description": "..."},
"인간관계운": {"score": 80, "description": "..."},
"keywords": ["행운", "성장"],
"lucky_items": ["루비"],
"caution": "주의사항"
}
"""
user_prompt = """
사용자 정보:
- 별자리: 물병자리
- 생년월일: 1990-01-15
- 라이프패스 넘버: 7
운세 카테고리: 사랑운
"""
system_prompt = """당신은 친근한 운세 상담사입니다.
사용자와 자연스럽게 대화하며 운세를 알려주세요.
"""
user_prompt = "오늘 중요한 면접이 있는데, 조언해줄 수 있어?"
| 카테고리 | 건수 | 비율 |
|---|---|---|
| 동양 운세 | 22,022 | 65.7% |
| - 사주 | 5,506 | 16.4% |
| - 타로 | 5,506 | 16.4% |
| - 주역 | 5,506 | 16.4% |
| - 성좌 | 5,504 | 16.4% |
| 서양 운세 | 11,506 | 34.3% |
| - 사랑운 | 2,301 | 6.9% |
| - 직업운 | 2,301 | 6.9% |
| - 건강운 | 2,301 | 6.9% |
| - 돈운 | 2,301 | 6.9% |
| - 인간관계운 | 2,302 | 6.9% |
| 총계 | 33,528 | 100% |
# rsLoRA 설정
lora_r: 64
lora_alpha: 128
lora_dropout: 0.05
target_modules: ["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"]
# 학습 설정
learning_rate: 2e-4
batch_size: 2
gradient_accumulation_steps: 4
epochs: 5
max_seq_length: 4096
warmup_steps: 100
weight_decay: 0.01
# 최적화
optimizer: adamw_8bit
scheduler: cosine
bf16: true
gradient_checkpointing: true
Unsloth의 <think> 태그 버그를 우회하기 위해 ChatML 포맷을 직접 구현:
def apply_chat_template_manual(messages: list[dict]) -> str:
"""Unsloth 버그 우회: 직접 ChatML 포맷 생성"""
formatted = ""
for msg in messages:
role = msg["role"]
content = msg["content"]
formatted += f"<|im_start|>{role}\n{content}<|im_end|>\n"
formatted += "<|im_start|>assistant\n"
return formatted
결과:
<think> 태그 완전 제거| 배치 크기 | Throughput | Latency (P50) | Latency (P99) |
|---|---|---|---|
| 1 | 28 tok/s | 1.2s | 1.8s |
| 4 | 95 tok/s | 1.5s | 2.3s |
| 8 | 160 tok/s | 1.9s | 3.1s |
테스트 환경:
| 메트릭 | 값 |
|---|---|
| JSON 파싱 성공률 | 99.8% |
| 스키마 검증 성공률 | 99.5% |
<think> 태그 출현 |
0% ✅ |
| 평균 응답 길이 | 1,200 토큰 |
| 모델 | 설명 | 크기 |
|---|---|---|
| yeji-4b-rslora-v8.1 | 현재 모델 (Full precision) | ~8GB |
| yeji-4b-rslora-v8-AWQ | AWQ W4A16 양자화 (구버전 기반) | ~1.5GB |
| yeji-4b-rslora-v8-AWQ-fixed | AWQ W4A16 vLLM 호환 (구버전 기반) | ~1.5GB |
권장:
yeji-4b-rslora-v8.1 (현재 모델)<think> 태그 출현 시
원인: Unsloth의 ChatML 구현 버그 해결: 이 모델(v8.1)은 직접 ChatML 포맷을 구현하여 이 문제가 해결됨
import json
import re
def extract_json(text: str) -> dict:
"""응답에서 JSON 추출"""
# 코드 블록 제거
text = re.sub(r"```json\s*", "", text)
text = re.sub(r"```\s*", "", text)
# JSON 파싱
return json.loads(text.strip())
# GPU 메모리 사용률 낮추기
vllm serve tellang/yeji-4b-rslora-v8.1 \
--gpu-memory-utilization 0.7 \
--max-model-len 2048
Apache-2.0 License
Base Model License: Qwen3-4B-Instruct (Tongyi Qianwen LICENSE)
@misc{yeji-4b-rslora-v8.1,
title={Yeji-4B-rsLoRA-v8.1: Korean Fortune-telling Language Model},
author={SSAFY YEJI Team},
year={2025},
publisher={HuggingFace},
url={https://huggingface.co/tellang/yeji-4b-rslora-v8.1}
}
Last Updated: 2025-02-01 Model Version: v8.1 Status: ✅ Production Ready
Base model
Qwen/Qwen3-4B-Instruct-2507