게임 제작 및 공유 메타버스 플랫폼인 메이플스토리 월드를 위해, Lua 스크립트 자동 완성 및 코드 예시 생성을 지원하는 Copilot 기능을 자체 파인튜닝 방식으로 개발하는 것을 목표로 한다.
이를 통해 다양한 도메인에 맞는 자동 코드 및 언어 생성 서비스가 가능하도록 한다.
| 구분 | Perplexity(PPL) | eval_loss |
|---|---|---|
| 학습 전 | 46.14 | 3.83 |
| 학습 후 | 5.34 | 1.68 |
Perplexity : math.exp(metrics["eval_loss"])
다음 토큰을 얼마나 자신 있게 예측하는지 나타내는 핵심 지표
- 아래와 같이 학습 후 플랫폼에 맞는 답변을 하는 것을 확인할 수 있다.
| 입력 prompt | 학습 전 답변 | 학습 후 답변 | 기대값 |
|---|---|---|---|
# 두 수를 더하는 함수를작성하시오.function add(a,b)\n return a + |
b end |
bend |
bend |
local currentTargetEntity = self.Entity.AI |
_TARGETif not target thenreturn nil, "No Target"end |
ChaseComponent:GetCurrentTarget() |
ChaseComponent:GetCurrentTarget()[코드 링크] |
local pages = _BadgeService: |
get_badge_pages(user) |
GetBadgesByUserId(userId) |
GetBadgeInfosAndWait() [코드 링크] |
self.ParticleComponent = |
ParticleComponentend |
self.Entity.ParticleComponent |
self.Entity.AreaParticleComponent[코드 링크] |
본 프로젝트는 넥슨의 메이플스토리 월드 플랫폼에서 사용되는 Lua 기반 스크립트 코드를 대상으로, 자동 코드 생성(Auto Code Generation) 기능을 개발하는 것을 목표로 한다. 게임 개발에 특화된 Lua 스크립트와 메이플스토리 월드의 고유한 API 및 구조를 학습 데이터로 활용하여, 사용자 입력에 따라 자동으로 Lua 코드를 생성해주는 시스템을 구현한다. 이를 통해 개발의 효율성을 높이는 것을 주요 기대 효과로 삼았다.
최근 IT 업계에서 각 기업들은 다양한 AI 프로젝트에 주목하고 있으며, 실제 공고나 프로젝트 사례를 살펴보면 코드 자동 생성, 자동 제안서 작성, 자동완성 등 사용자 입력을 기반으로 효율성을 높이는 시스템에 대한 수요가 꾸준히 증가하고 있음을 확인할 수 있습니다. 이러한 트렌드에 맞추어, 본 프로젝트에서는 자동 코드 생성 기능에 집중해보기로 결정하였습니다.
대상 플랫폼으로 메이플스토리 월드를 선정한 이유는, 이 플랫폼이 Lua를 기반으로 하면서도 자체적으로 커스텀된 문법, API, 함수 등을 폭넓게 제공하기 때문입니다. 또한, 활발한 개발자 커뮤니티와 풍부한 예시 코드 자료를 활용하여 충분한 학습 데이터를 수집할 수 있다는 점도 중요한 선정 기준이 되었습니다.
이러한 특성 덕분에 실제로 플랫폼별 특화 학습이 가능한지 확인할 수 있고, 자동 생성된 코드의 평가와 검증 과정도 상대적으로 명확하게 진행할 수 있다고 판단했습니다.
-
개발 및 실험 환경
- Google Colab (모델 파인튜닝/실험)
-
프로그래밍 언어
- Python (데이터 수집, 처리, 학습 파이프라인)
- Lua (생성되는 스크립트 및 평가용)
-
데이터 수집/구성
- BeautifulSoup (참고 사이트 크롤링, API·예시 코드 수집)
- Perplexity 서비스(데이터셋 변환, jsonl 저장)
-
학습 데이터
- Lua 기반 예시 코드, API 문서 등
- 메이플스토리월드 API 레퍼런스
-
모델 및 배포
- Qwen/Qwen2.5-Coder-1.5B-Instruct
- nuprl/MultiPLCoder-1b
- Hugging Face로 모델 다운로드 및 배포
-
결과 활용/실행 환경
- VS Code
- Ollama, Continue(모델 연동 및 코드 테스트)
# Load model directly
from transformers import AutoTokenizer, AutoModelForCausalLM
tokenizer = AutoTokenizer.from_pretrained("bangill/maplestoryworlds-lua-api-finetune")
model = AutoModelForCausalLM.from_pretrained("bangill/maplestoryworlds-lua-api-finetune")- model과 tokenizer 모두 같은 곳에 저장 필요
model.save_pretrained("폴더 주소", safe_serialization=True)
tokenizer.save_pretrained("폴더 주소", safe_serialization=True)- 위에서 저장한 폴더를 사용해서 gguf 파일로 변환
git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp
pip install -r requirements.txt
python convert-hf-to-gguf.py ../merged --outfile ../"원하는 모델명".gguf
📦auto_gen_lang
┣ 📂1_train_data # 학습용 데이터 셋
┣ 📂api # 개발자 레퍼런스 사이트에 있는 api 종류. 예시 코드들 저장. 빈 파일은 예시 코드가 없다.
┃ ┣ 📂components_api
┃ ┣ 📂enums_api
┃ ┣ 📂logics_api
┃ ┣ 📂lua_api
┃ ┣ 📂miscs_api
┃ ┗ 📂services_api
┣ 📂huggingface # HuggingFace model에 README.md 업로드
┃ ┣ 📜hf_readme_upload.py
┃ ┗ 📜README.md
┣ 📜README.md
┣ 📜tree.txt
┣ 📜url_content.py # api 종류 저장 기능 코드
┗ 📜url_content_example_chrolling.py # 예시 코드 저장 기능 코드
- 선정 모델
- Qwen/Qwen2.5-Coder-1.5B-Instruct : 지난 한달 동안 10만 이상 다운로드를 할만큼 최근 개발자들 사이에서 경량화 llm 중 높은 성능을 보이는 모델이다. 대중적인 c, python, java와 같은 코드를 학습했을 뿐 아니라 lua언어도 학습을 진행하였기 때문에 모델로 선정하였다. 뿐만 아니라 어댑터를 활용하여 ollama를 이용하려면 ollama에서 서비스 하는 모델을 사용해야하는 제약이 있었다.
- nuprl/MultiPLCoder-1b : 소형 llm임에도 불구하고, lua 같은 저자원 언어에 특화되어있다는 평이 있다. 즉, 이용 횟수는 적지만 현재 상황에 맞는 모델이라 선정하였다.
url_content.py 파일 활용
- Components (100개)
- "AIChaseComponent","AIComponent", ...
- Events (179개)
- "ActionStateChangedEvent","AnimationClipEvent", ...
- Services (38개)
- "BadgeService","CameraService", ...
- Logics (8개)
- "DefaultUserEnterLeaveLogic","Logic", ...
- Misc (94개)
- "AnimationClip","AvatarBodyActionElement", ...
- Enums (94개)
- "AccountRegion","AccountTrustLevel", ...
- Lua (7개)
- "global","math", ...
url_content_example_chrolling.py 파일 활용
각 카테코리에 맞게 폴더에 txt 파일로 예시 코드들 수집한다. 예시 코드가 없는 경우가 있어서, 빈 파일이 존재한다.
생성된 txt 파일을 활용하여 LLM에 입력하고, 나온 값들을 데이터셋으로 활용한다. 사용한 모델과 프롬프트는 다음과 같다.
모델 : OpenAI GPT-4 Turbo
엔진 : Perplexity
프롬프트 :
아래 규칙에 따라 소스코드를 자동 코드 완성 모델의 fine-tuning용 dataset으로 변환해줘.
- 각 prompt는 사용자가 실제 자동완성을 원할 만한 코드의 한 줄 또는 짧은 블록으로(줄바꿈 포함 가능), 앞에 오는 코드만 담는다.
- 각 completion은 그 prompt 다음 작성될 실제 코드(한 줄 또는 코드 블록)로, 줄바꿈이 필요하면 포함하게 해라.
- 불필요한 설명, 선언, 주석, 빈 줄 등은 모두 제외한다.
- prompt, completion을 key로 갖는 jsonl 샘플만 출력하라(예시 및 설명 없이 코드만).
아래의 소스를 위 기준에 맞춰 변환하라.
소스:
<여기에 붙여넣기>
이렇게 생성된 jsonl 파일을 1_train_data 폴더에 저장하여 추후 학습시에 불러내어 사용한다.
PEFT란? : Parameter-Efficient Fine-Tuning 으로 계산 효율성과 자원 절약을 위해 쓰이는 방법
LoRA란? : Low-Rank Adaptation 으로 소수의 파라미터만 업데이트하는 방식
from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import LoraConfig, get_peft_model
lora_config = LoraConfig(
task_type="CAUSAL_LM", # 텍스트 생성(코드 완성) 작업 유형
inference_mode=False, # 훈련 모드로 설정
target_modules=["c_fc", "c_attn", "c_proj"],
# c_attn : 입력을 attention에 맞게 쿼리로 변환하는 레이어,
# c_proj : attention 결과를 다시 모델 내부 차원으로 투영하는 레이어,
# c_fc : 입력을 변화하는 레이어
# 주요 동자과 의미를 담당하는 부분만 업데이트.
r=16, # Low-rank 행렬의 차원. (보통 8, 16, 32 사용)
lora_alpha=32, # 스케일링 요소. 보통 r의 2배 또는 4배로 설정.
lora_dropout=0.1, # LoRA 레이어에 적용할 드롭아웃 비율
)
model = get_peft_model(model, lora_config)
from transformers import Trainer, TrainingArguments
training_args = TrainingArguments(
output_dir="./multiplcoder-1b-lua-finetuned", # 모델 체크포인트와 로그 파일 등이 저장되는 디렉토리 경로
per_device_train_batch_size=1, # 각 GPU/CPU별로 한 번에 처리할 데이터 샘플 개수
gradient_accumulation_steps=4, # 미니배치 여러 번의 결과를 합산해서 한 번만 파라미터 업데이트
max_steps=500, # 최대 학습 반복 횟수
save_steps=200, # 지정 step마다 체크포인트 저장.
save_total_limit=2, # 저장할 체크포인트 모델 개수. 오래된 것은 삭제.
num_train_epochs=3, # 전체 데이터셋을 몇 반복할지.
weight_decay=0.01, # 과적합 방지 규제 인자.
warmup_ratio=0.03, # 초기 학습률 상승 규제 인자.
logging_steps=10, # 로그 출력 정보
bf16=False, # 연산에 부동소수점 계산을 사용할지 설정.
fp16=True,
optim="adamw_torch",
report_to=[],
learning_rate=1.2e-4, # 학습률 지정
)
trainer = Trainer(
model=model,
args=training_args, # 위에서 설정한 세부 argument
train_dataset=train_dataset,
eval_dataset=eval_dataset,
data_collator=data_collator,
processing_class=tokenizer,
)
결과에 가장 많은 영향을 끼친 파라미터 : model, max_steps, learning_rate
모델 Qwen/Qwen2.5-Coder-1.5B-Instruct 에서 모델 nuprl/MultiPLCoder-1b 으로 변경하였더니 learning rate 1.5e-4 기준 학습후 eval_loss가 2.586 에서 1.66으로 감소
모델 nuprl/MultiPLCoder-1b 기준, max_steps를 200에서 500으로 변경하였더니 Training Loss가 1.83 에서 1.14 까지 감소
rate를 높일수록 eval_loss가 줄어드는 것을 확인. 하지만, 어느 정도 이상이 되면 과적합이 일어나면서 파멸적 망각 상태가 되는 것을 확인. 이전에 학습되어 잘 결과가 나오던 것이 추가 학습후 잊어버리는 상황.
ollama와 vscode의 확장 프로그램 continue를 활용해서 연동.
# Modelfile
FROM ./"원하는 모델명".gguf # FROM ./maple_finetune.gguf
PARAMETER temperature 0.2
TEMPLATE "{{ .System }}\n{{ .Prompt }}"
SYSTEM "You are a helpful coding assistant."
- ollama 다운 필요
ollama serve
ollama create "알아보기 위한 모델명 지정" -f ./Modelfile
# ollama create "maple_finetune" -f ./Modelfile
ollama run "알아보기 위한 모델명 지정"
- config.yaml 파일 작성
name: My Local Config
version: 1.0.0
schema: v1
models:
- name: "실행시킬 모델명" # name: maple_finetune
provider: ollama
model: "실행시킬 모델명" # 만약 version이 있다면 명시. model : maple_finetune:latest
# Ollama 서버 주소를 명시적으로 지정합니다.
apiBase: http://127.0.0.1:11434
# 요청 타임아웃을 60초(60000ms)로 넉넉하게 설정합니다.
requestOptions:
timeout: 60000
roles:
- autocomplete
Continue 설정 파일 config.yaml는 사용자 홈 디렉터리의 .continue 폴더에 있으며, 없으면 새로 생성하면 된다.
작업하는 root에서 .continuerc.json으로 프로젝트별 설정도 가능.
- 로컬에서 원하는 모델을 사용하기 위해 ollama 서비스를 사용하기로 결정. 뿐만 아니라, 좀 더 편리하게 사용하려면 ollama에서 서비스하고 있는 모델을 사용하고 adapter를 활용하여 파인튜닝한 모델을 사용할 수 있었다. 하지만 빌드를 할 때 adapter 사용에 문제가 발생.
- 뿐만 아니라, adapter를 사용하지 않음으로써 똑같이 gguf 파일로 모델을 저장하여 local에서 서빙해서 사용하게 되었다. 따라서 lua에 특화되어 학습된 nuprl이 더 적합하다고 판단하여 변경하였다.
- 코드 자동 생성이기 때문에 정제되고, 정갈한 코드 예시들이 필요했다. 하지만 github에
maplestory-world,maplestoryworld,nexon으로 검색한 결과 lua 언어로 생성된 레포지토리가 20개 남짓뿐이라 학습할 수 있는 데이터들이 매우 적었다. - 개발자 api reference 사이트에서 예시 코드들을 활용하였지만, 적합한 dataset을 구성하기 어려웠다. 실제 사용하는 코드들이 아니기 때문에, 어떤 코드가 maplestory world만의 특별한 코드인지 파악할 수 없었다. 따라서 학습의 정도와 예시 데이터를 명확하게 구분하지 못하여 학습용 데이터 세트의 질이 낮았다고 판단된다.
save_pretrained를 통해 저장된 토크나이저와 모델을 바로 사용할 수 없고, 한 번 더 변환을 거쳐야 하는 것이 번거로웠다.- 하지만 이미 서비스 되어있는 모델이거나, gguf 파일로 변환된 모델이라면 쉽게 호출할 수 있어서 편리하였다.
github copilot이 있다면 충돌이 일어나는 듯한 버벅임이 있었다. copilot도 삭제하고, continue도 삭제하고 다시 설치하였다.- ollama의 모델과 continue와 연동을 하고자 config 파일을 수정해야한다. 일부 문서에서는 config.ts 파일을 생성 및 수정하게 안내를 하는데, 버전에 따라 맞지 않은 경우가 있는 듯하다. 본 프로젝트에서는 config.ts 파일이 아닌 config.yaml 파일을 사용하였다.
- google colab에서 prompt를 통해 얻은 답변과 학습된 해당 모델을 continue에 연결해서 얻어낼 때의 답변이 상이하다. 그 이유는 코드로 프롬프트를 통해 생성할 때는 generate할 때 세세하게 파라미터를 설정할 수 있는 반면, continue에서는 그러지 못해 생기는 차이라 판단하였다.
- lua 언어 자체가 저자원 언어이기 때문에 제대로 학습이 된 모델이 없었다. 따라서
function add(a,b)\n return a+와 같은 프롬프트를 작성했을 때function add(a,b)\n return a+10*b \nend와 같이 일반적이지 않은 답변을 하기도 하였다.# 두 수를 더하는 함수를 작성하시오. function add(a,b)\n return a +와 같이 주석으로 설명을 추가해야만 정확한 답변이 가능하였다.
모델을 등록하였고, 기존 모델과 학습된 모델의 답변을 비교할 때는 AutoTokenizer,AutoModelForCausalLM,from_pretrained 를 활용하여 모델 사용을 하였다.
링크 : https://huggingface.co/bangill/maplestoryworlds-lua-api-finetune
결과적으로는 학습된 모델을 활용해서 continue 프로그램에 연결을 하여 vs code에서 test를 진행할 수 있었다. 하지만 위에서 언급된 것처럼 colab에서 이루어지는 답변과 차이가 있었다.
- function add
- current target entity
- 충분히 다양한 직군에서 활용할 수 있을 것이라고 판단된다. Lua 라는 저자원 언어를 가지고 파인 튜닝을 통해 학습시켜 어느 정도의 성과를 낼 수 있었기 때문이다. 이와 마찬가지로 회사의 보고서 양식이나 단어들을 학습시켜서 일반 LLM 모델과의 차이를 둘 수 있을 것이라 생각한다.
- 파인 튜닝 뿐만 아니라 RAG를 활용해서 결과값을 도출하는 방법도 있다. 현재, 현업에 있는 개발자가 말하길 파인 튜닝도 좋지만 RAG를 사용을 고민하고 있는 상황이라고 하였다. 하지만 이 같은 경우에는 현 프로젝트처럼 continue 프로그램을 활용하기 보다는 회사의 자체 RAG 기반 LLM을 탑재한 프로그램을 개발하여야 한다.
- 쓰레기 데이터를 넣으면 쓰레기 모델이 나온다. 학습을 시킬 때 정제되지 않은 프롬프트와 컴플리션을 제공하였기 때문에 추후 모델을 사용할 때도 답변을 진행할 때 깔끔하지 않은 답변이 제공되기도 하였다. 이는 실제 사용하는 언어가 아니라 무엇이 중요하고 필요한 코드인지 검사를 하지 않았기 때문이다. 실제 회사에서 적용할 때는 데이터셋부터 현업자의 협업이 필요하다.
https://github.com/ggerganov/llama.cpp
https://apidog.com/kr/blog/how-to-download-and-use-ollama-kr/
https://goddaehee.tistory.com/381
https://hyunicecream.tistory.com/123
https://maplestoryworlds-creators.nexon.com/ko/apiReference/How-to-use-API-Reference
https://ysg2997.tistory.com/11
https://huggingface.co/Qwen/Qwen2.5-Coder-1.5B-Instruct
https://huggingface.co/nuprl/MultiPL-T-StarCoderBase_1b
https://docs.continue.dev/reference
Vaswani, A., Shazeer, N., Parmar, N., Uszkoreit, J., Jones, L., Gomez, A. N., ... & Polosukhin, I. (2017). Attention is all you need. Advances in neural information processing systems, 30.