MiniMind — 작은 언어 모델을 처음부터 끝까지 직접 학습한다
MiniMind 의 LoRA 로 말투만 바꾸고, 기반 가중치가 그대로인지 확인한다
목표
MiniMind model_lora.py 의 apply_lora·save_lora·load_lora 로 SFT 기준 모델에 rank 8 LoRA 를 얹는다. 기반 가중치를 얼리고 LoRA 만 학습해 답을 '…다냥.' 말투로 바꾸고, 기반 가중치가 한 비트도 바뀌지 않았는지와 합친 모델이 같은 출력을 내는지 확인한다.
왜 중요한가
LoRA 의 약속은 두 가지다 — 적은 파라미터만 학습한다, 기반 모델은 그대로 둔다. 앞의 것은 메모리와 저장을 줄이고, 뒤의 것은 한 기반 위에 LoRA 여럿을 갈아 끼울 수 있게 한다. 그런데 얼리기를 빼먹으면 두 약속이 모두 조용히 깨진다. 학습은 잘 되고 말투도 바뀌지만 기반 가중치까지 움직여 다른 LoRA 가 모두 틀어진다. 그래서 이 실습은 결과(말투)만 보지 않고, 학습 뒤의 기반 가중치를 원본과 한 텐서씩 견준다. MiniMind 의 구현은 알파 배율이 없고 차원이 같은 Linear 에만 붙는다는 점도 코드로 확인한다.
단계
/opt/mm/ref/sft.pth를 불러apply_lora(model, rank=8)을 하고, LoRA 가 붙은 모듈 이름을 /root/mm/lora/targets.json 에rank·targets로 적으세요.- 얹기 전과 직후의 로짓 최대 차이를 /root/mm/lora/zero.json 에
max_abs_diff로 적으세요. - 이름에
lora가 없는 파라미터를 모두 얼리고, 학습하는 파라미터 수·전체 수·비율을 /root/mm/lora/params.json 에 적으세요. lora_nyang.jsonl로 LoRA 만 150걸음 학습해save_lora로 /root/mm/lora/lora.pth 를 저장하세요.- 학습이 끝난 모델에서 LoRA 를 뺀 가중치를 /root/mm/lora/base_after.pth 로 저장하세요. 원본 SFT 가중치와 같아야 합니다.
- 떼어 둔 질문(
lora_nyang_val.jsonl)에서 '냥.' 으로 끝나는 답의 비율을 LoRA 없이·있이 재어 /root/mm/lora/effect.json 에 적으세요. - W + B·A 로 합친 가중치를 /root/mm/lora/merged.pth 로 저장하세요.
- /root/mm/lora/report.md 에
## 어디에 붙나## 무엇이 바뀌나## 합치기세 절을 쓰고, 학습한 파라미터 수와 LoRA 를 얹은 뒤의 '냥' 비율을 넣으세요.
참고
from model.model_lora import apply_lora, save_lora, load_lora—/opt/minimind/model/model_lora.py를 직접 열어 보세요. 60줄입니다.- SFT 라벨은
mmkit.sft_encode(tok, 대화)가 SFT 모듈에서 만든 것과 같은 규칙으로 만듭니다. - 150걸음은 노드에서 10초 안팎입니다. 떼어 둔 질문 60개의 생성은 두 번에 5초쯤 걸립니다.
- 흔한 실수:
requires_grad=False로 얼리지 않은 채 옵티마이저에model.parameters()를 통째로 넘기는 것(기반 가중치까지 움직입니다), 얼렸더라도 기반 파라미터의 기울기를 계산하느라 메모리를 쓰는 것,save_lora대신 전체 state_dict 를 저장하는 것. - 원문: model_lora.py · train_lora.py · 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.json 에 max_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.jsonl 을 mmkit.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 를 바꿀 때 무엇을 다시 봐야 하는지도 한 줄 적어 보세요.