LabHub
はじめる
배우기 러닝패스 코스

MiniMind — 小さな言語モデルを最初から最後まで自分で学習する

MiniMind の LoRA で口調だけを変え、基盤の重みがそのままか確かめる

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

MiniMind model_lora.pyapply_lora·save_lora·load_lora 로 SFT 기준 모델에 rank 8 LoRA 를 얹는다. 기반 가중치를 얼리고 LoRA 만 학습해 답을 '…다냥.' 말투로 바꾸고, 기반 가중치가 한 비트도 바뀌지 않았는지와 합친 모델이 같은 출력을 내는지 확인한다.

왜 중요한가

LoRA 의 약속은 두 가지다 — 적은 파라미터만 학습한다, 기반 모델은 그대로 둔다. 앞의 것은 메모리와 저장을 줄이고, 뒤의 것은 한 기반 위에 LoRA 여럿을 갈아 끼울 수 있게 한다. 그런데 얼리기를 빼먹으면 두 약속이 모두 조용히 깨진다. 학습은 잘 되고 말투도 바뀌지만 기반 가중치까지 움직여 다른 LoRA 가 모두 틀어진다. 그래서 이 실습은 결과(말투)만 보지 않고, 학습 뒤의 기반 가중치를 원본과 한 텐서씩 견준다. MiniMind 의 구현은 알파 배율이 없고 차원이 같은 Linear 에만 붙는다는 점도 코드로 확인한다.

단계

  1. /opt/mm/ref/sft.pth 를 불러 apply_lora(model, rank=8) 을 하고, LoRA 가 붙은 모듈 이름을 /root/mm/lora/targets.jsonrank·targets 로 적으세요.
  2. 얹기 전과 직후의 로짓 최대 차이를 /root/mm/lora/zero.jsonmax_abs_diff 로 적으세요.
  3. 이름에 lora 가 없는 파라미터를 모두 얼리고, 학습하는 파라미터 수·전체 수·비율을 /root/mm/lora/params.json 에 적으세요.
  4. lora_nyang.jsonl 로 LoRA 만 150걸음 학습해 save_lora/root/mm/lora/lora.pth 를 저장하세요.
  5. 학습이 끝난 모델에서 LoRA 를 뺀 가중치를 /root/mm/lora/base_after.pth 로 저장하세요. 원본 SFT 가중치와 같아야 합니다.
  6. 떼어 둔 질문(lora_nyang_val.jsonl)에서 '냥.' 으로 끝나는 답의 비율을 LoRA 없이·있이 재어 /root/mm/lora/effect.json 에 적으세요.
  7. W + B·A 로 합친 가중치를 /root/mm/lora/merged.pth 로 저장하세요.
  8. /root/mm/lora/report.md## 어디에 붙나 ## 무엇이 바뀌나 ## 합치기 세 절을 쓰고, 학습한 파라미터 수와 LoRA 를 얹은 뒤의 '냥' 비율을 넣으세요.

참고

LoRA 는 어디에 붙나

mmkit.load_model("/opt/mm/ref/sft.pth") 로 불러 apply_lora(model, rank=8) 을 한 뒤, lora 속성이 생긴 모듈의 이름을 순서대로 /root/mm/lora/targets.json{"rank": 8, "targets": [...]} 로 적으세요.

model.named_modules() 를 돌며 hasattr(m, 'lora') 인 것을 모으면 됩니다. apply_lora 는 in_features == out_features 인 Linear 에만 붙습니다 — 우리 모델에서 어느 투영이 그 조건을 만족하는지 먼저 짐작해 보세요.

얹은 직후에는 아무것도 바뀌지 않는다

/opt/mm/ref/val.npy 앞 64토큰에 대한 로짓을 apply_lora 전과 직후에 구해 최대 절대 차이를 /root/mm/lora/zero.jsonmax_abs_diff 로 적으세요.

MiniMind 의 LoRA 는 A 를 정규분포로, B 를 0 으로 초기화합니다. B·A 가 0 이라 곁가지의 출력이 0 이고, 학습은 '기반 모델에서 얼마나 벗어날까' 를 0 부터 배웁니다.

LoRA 만 학습하도록 얼린다

학습 스크립트 /root/mm/lora/train_lora.py 를 쓰기 시작하세요 — LoRA 를 얹은 모델에서 이름에 lora 가 없는 파라미터는 requires_grad=False, 있는 것은 True 로 두고, 학습하는 파라미터 수(trainable)·전체 수(total)·비율(ratio)을 /root/mm/lora/params.json 에 적으세요.

MiniMind 의 train_lora.py 가 쓰는 규칙 그대로입니다. 곳마다 A(8×128) + B(128×8) 이고 붙은 곳의 수를 곱하면 됩니다. 전체에는 LoRA 파라미터도 들어갑니다.

말투를 LoRA 로 배운다

/opt/mm/data/lora_nyang.jsonlmmkit.sft_encode 로 바꿔, LoRA 파라미터만 AdamW(lr 5e-3)에 넘겨 150걸음(배치 16·시드 42) 학습하고 save_lora(model, "/root/mm/lora/lora.pth")/root/mm/lora/lora.pth 를 저장하세요.

save_lora 는 LoRA 가 붙은 모듈마다 …lora.A.weight·…lora.B.weight 만 fp16 으로 저장합니다. 파일 크기를 기반 모델(4MB 남짓)과 견줘 보세요. 채점기는 열쇠가 LoRA 뿐인지, B 가 0 에서 움직였는지 봅니다.

기반 가중치는 그대로인가

학습이 끝난 모델의 state_dict 에서 이름에 .lora. 가 든 것을 뺀 나머지를 /root/mm/lora/base_after.pth 로 저장하세요. 채점기가 원본 /opt/mm/ref/sft.pth 와 텐서마다 견줍니다.

얼리기가 제대로 됐다면 한 비트도 다르지 않습니다. requires_grad 를 끄지 않고 옵티마이저에 모든 파라미터를 넘겼다면 기반 가중치도 기울기를 받아 움직여 여기서 걸립니다. PyTorch 옵티마이저는 기울기가 없는(None) 파라미터를 건너뛰므로, 얼리기만 제대로 하면 옵티마이저에 무엇을 넘겼든 기반은 그대로입니다.

말투가 얼마나 바뀌었나

lora_nyang_val.jsonl 의 질문 60개에 욕심쟁이로 답하게 해 '냥.' 으로 끝나는 답의 비율을 기반 모델(base_rate)과 LoRA 를 얹은 모델(lora_rate)에서 재어 /root/mm/lora/effect.json 에 적으세요.

LoRA 를 얹은 모델은 기반 모델을 불러 apply_lora(rank=8)load_lora(model, 경로) 로 만듭니다. 떼어 둔 질문은 LoRA 학습 때 본 적 없는 조합이라, 말투가 '질문 모양' 이 아니라 '답하는 방식' 으로 배워졌는지를 봅니다.

합쳐서 곁가지를 없앤다

lora.pth 의 A·B 로 해당 모듈의 가중치에 B @ A 를 더해(알파 배율 없이) 합친 state_dict 를 /root/mm/lora/merged.pth 로 저장하세요. 채점기는 이 가중치를 LoRA 없는 모델에 넣어, LoRA 를 얹은 모델과 같은 로짓을 내는지 봅니다.

A 의 모양은 (rank, 입력), B 는 (출력, rank) 라 B @ A 가 (출력, 입력) — 원래 weight 와 같은 모양입니다. MiniMind 의 merge_lora 도 같은 일을 하고 fp16 으로 저장합니다. 여기서는 비교를 위해 fp32 로 저장합니다.

LoRA 의 약속을 확인한 기록

/root/mm/lora/report.md## 어디에 붙나 ## 무엇이 바뀌나 ## 합치기 세 절을 쓰고, 3단계의 trainable 과 6단계의 lora_rate 를 숫자로 넣으세요.

LoRA 파일이 기반 모델의 몇 분의 일인지, 알파 배율이 없는 구현이라 rank 를 바꿀 때 무엇을 다시 봐야 하는지도 한 줄 적어 보세요.