MiniMind — 작은 언어 모델을 처음부터 끝까지 직접 학습한다
MiniMind 의 학습 루프로 300걸음 사전학습하고 손실 곡선을 읽는다
목표
MiniMind train_pretrain.py 의 뼈대(AdamW · MiniMind 코사인 학습률 · 기울기 자르기 1.0)를 CPU 크기로 줄인 학습 루프를 직접 쓰고, 시드를 고정해 재현되는지 확인한 뒤 300걸음을 학습한다. 손실 곡선을 숫자로 읽고, 학습률이 너무 크면 무엇이 망가지는지 본다.
왜 중요한가
학습 루프의 핵심은 두 줄이지만, 결과를 가르는 것은 그 주변의 결정이다 — 학습률과 그 일정, 기울기 자르기, 시드, 검증 손실을 재는 주기. 이 결정이 틀리면 몇 시간을 돌린 뒤에야 알게 된다. 그래서 몇 분짜리 학습으로 곡선의 모양을 먼저 확인하는 습관이 필요하다. 손실 곡선은 모델이 무엇을 먼저 배우는지도 보여 준다. 처음 수십 걸음은 토큰 빈도를, 그 뒤로는 문맥에 따른 규칙을 배운다. 문맥 없이 빈도만 아는 모델의 손실(유니그램 엔트로피)과 견주면 곡선의 어느 부분이 '문맥을 쓰기 시작한' 구간인지 알 수 있다.
단계
- /root/mm/pre/train.py 를 쓰고(인자
--steps·--lr·--seed·--eval-every·--out·--log), 같은 시드로 20걸음을 두 번 돌려 /root/mm/pre/log_a.csv·/root/mm/pre/log_b.csv 를 남기세요. - 300걸음(배치 8·길이 128·lr 3e-3)을 학습해 가중치를 /root/mm/pre/ckpt.pth, 기록을 /root/mm/pre/log.csv 에 남기세요. 검증 손실이 1.6 아래로 내려와야 합니다.
log.csv에서 곡선의 모양을 뽑아 /root/mm/pre/curve.json 에 적으세요.- 학습 배열의 유니그램 엔트로피를 /root/mm/pre/unigram.json 에 적으세요.
- lr 0.05 로 50걸음을 돌려 /root/mm/pre/log_high.csv 를 남기세요.
ckpt.pth로 '가람 마을의' 뒤를 16토큰 욕심쟁이로 이어 써 /root/mm/pre/sample.txt 에 저장하세요.- /root/mm/pre/report.md 에
## 손실 곡선## 학습률## 이어 쓰기세 절을 쓰고, 마지막 검증 손실과 유니그램 엔트로피를 숫자로 넣으세요.
참고
- 기록 칼럼은
step,train_loss,val_loss,lr입니다. 검증 손실을 재지 않은 줄은val_loss를 비워 둡니다. 검증 손실은mmkit.lm_loss(model, val, n_batches=4)로 재면 채점기와 같은 방법입니다. - 학습률은
mmkit.minimind_lr(step, 전체 걸음, lr)이 MiniMindget_lr과 같은 식을 돌려줍니다. 걸음 수는 1 부터 셉니다. - 300걸음은 노드에서 20초 안팎입니다(파드 CPU 2개). 파드가 붐비는 노드에 뜨면 몇 배 걸릴 수 있습니다.
- 흔한 실수: 시드를 모델 초기화에만 걸고 배치 뽑기에는 안 거는 것,
model.eval()로 바꿔 검증한 뒤model.train()으로 되돌리지 않는 것,torch.save(model)처럼 모델 객체째 저장하는 것(state_dict()를 저장하세요). - 원문: train_pretrain.py · trainer_utils.py · PyTorch AdamW · clip_grad_norm_
같은 시드면 같은 손실
/root/mm/pre/train.py 를 쓰세요. /opt/mm/ref/config.json 으로 모델을 만들고 /opt/mm/ref/train.npy 에서 임의 위치의 128토큰을 배치 8개씩 뽑아 학습하며, 걸음마다 step,train_loss,val_loss,lr 을 --log 경로의 CSV 로 남깁니다. --steps 20 으로 두 번 돌려 /root/mm/pre/log_a.csv·/root/mm/pre/log_b.csv 를 만드세요.
재현하려면 두 가지 난수를 다 고정해야 합니다 — 가중치 초기화(mmkit.seed_all(시드) 를 모델을 만들기 전에)와 배치 뽑기(torch.Generator().manual_seed(시드) 를 randint 에 넘기기). 첫 걸음 손실은 ln 1024 ≈ 6.93 근처여야 합니다.
300걸음 사전학습
train.py 로 300걸음(배치 8·길이 128·lr 3e-3·시드 42, 50걸음마다 검증)을 학습해 가중치를 /root/mm/pre/ckpt.pth(model.state_dict()), 기록을 /root/mm/pre/log.csv 에 남기세요.
학습률은 MiniMind 의 코사인(mmkit.minimind_lr)으로 첫 걸음에 3e-3, 마지막에 3e-4 가 되어야 합니다. 채점기는 ckpt.pth 를 불러 검증 손실을 다시 재어 1.6 아래인지, 그리고 기록의 마지막 val_loss 와 같은지를 봅니다.
곡선의 모양을 숫자로
log.csv 에서 첫 손실(first_loss), 학습 손실이 처음으로 3 아래가 된 걸음(step_below_3), 마지막 학습 손실(final_train), 마지막·최저 검증 손실(final_val·best_val)을 /root/mm/pre/curve.json 에 적으세요.
급하게 떨어지는 구간이 몇 걸음에서 끝나는지 보세요. 그 뒤로는 같은 양을 내리는 데 훨씬 많은 걸음이 듭니다.
빈도만 아는 모델의 손실
/opt/mm/ref/train.npy 의 토큰 빈도로 유니그램 엔트로피(−Σ p·ln p, 나트)를 계산해 /root/mm/pre/unigram.json 에 unigram_entropy 로 적으세요.
문맥을 전혀 보지 않고 늘 같은 분포(토큰 빈도)를 내놓는 모델의 교차 엔트로피가 이 값입니다. 곡선이 이 값보다 아래로 내려가면 모델이 앞 토큰을 보고 다음을 좁히기 시작했다는 뜻입니다. numpy.bincount 가 빠릅니다.
학습률이 너무 크면
train.py 를 --lr 0.05 --steps 50 으로 돌려 /root/mm/pre/log_high.csv 를 남기세요. 같은 시드이므로 2단계 기록의 41–50걸음과 견줄 수 있습니다.
큰 학습률은 처음 몇 걸음은 빨리 내려가는 듯하다가 한 번 크게 튄 뒤 어느 값에 붙어 내려오지 않습니다. 기울기 자르기(1.0)도 걸음의 크기까지 막아 주지는 못합니다 — 걸음의 크기는 lr 이 정합니다.
배운 것으로 이어 쓰기
ckpt.pth 를 불러 [bos] + '가람 마을의' 뒤를 16토큰 욕심쟁이(do_sample=False, top_k=0, top_p=1.0, eos_token_id=2)로 이어 쓰고, bos 를 뺀 전체 글을 /root/mm/pre/sample.txt 에 저장하세요.
mmkit.load_model(경로) 로 불러 MiniMind 의 model.generate 를 부르면 됩니다. 욕심쟁이 생성은 같은 가중치면 늘 같은 글을 내므로, 채점기가 여러분의 ckpt.pth 로 다시 생성해 견줍니다. 사전학습만 한 모델은 질문에 답하지 않고 말뭉치처럼 이어 씁니다.
곡선을 읽은 기록
/root/mm/pre/report.md 에 ## 손실 곡선 ## 학습률 ## 이어 쓰기 세 절을 쓰고, 3단계의 final_val 과 4단계의 unigram_entropy 를 숫자로 넣으세요.
곡선의 어느 구간이 빈도를, 어느 구간이 문맥을 배운 것인지 유니그램 엔트로피를 기준으로 한 줄 적으세요. 학습률 절에는 5단계에서 본 것을 적습니다.