C.W.K.
Stream
Lesson 06 of 07 · published

nn.Module — 의미 있는 가장 작은 네트워크

~18 min · nn.Module, training-loop, optimizer

Level 0Curious
0 XP0/51 lessons0/15 achievements
0/100 XP to next level100 XP to go0% complete

End-to-end 로 뭘 할 거야

이 레슨 끝에 너는 MLX 안에 진짜, 돌아가는, 완전한 training loop 가질 거 — 작은 2-layer MLP 정의, synthetic regression dataset (line + noise) 생성, SGD 로 모델 학습, loss 떨어지는 거 봐, 추론 돌려. 약 30 줄의 코드, 다 production-shape, toy 아님.

핵심은 흥미로운 모델 배우는 게 아냐 — MLX training loop 의 모든 관절이 딸깍 맞물리는 걸 느끼는 것. 모든 후속 트랙이 네가 이걸 한 번 느꼈다고 가정.

조각들

  • nn.Module — 어떤 모델이든 base class. Subclass 하고, submodule 가진 __init__ 와 forward pass 가진 __call__ 정의. 별도 forward method 없음 — Python 의 __call__ 로 충분.
  • nn.Linear, nn.relu — 우리에게 필요한 layer type 과 activation.
  • mlx.optimizers.SGD — 가장 단순한 optimizer. .update(model, grads) method 가 gradient 를 in place 로 적용.
  • nn.value_and_grad(model, loss_fn) — 한 호출에 모델 parameter 에 대한 loss 와 gradient 둘 다 받는 canonical 패턴.
  • mx.eval(model.parameters(), optimizer.state) — 각 step 끝에 업데이트된 weight 와 optimizer state materialize. MLX 가 step 을 fuse 하게 해주는 lazy-eval 경계.

전체 loop

아래 코드 블록이 training pipeline 전체. 모양 한 번 읽어 — 얼마나 짧은지 봐 — 그리고 돌려. Loss 숫자 떨어지는 거 봐. 마지막 추론 호출이 진짜 target (2.0 × 1.0 + 0.5 = 2.5) 의 외칠 수 있는 거리 안의 값 돌려주는 거 알아채.

여기서 가져갈 것

모양이 진짜라서 toy 가 아냐. Track 4 의 70B 모델 fine-tune 도 정확히 이 골격 쓸 거 — model subclass, nn.value_and_grad, optimizer.update, 경계의 mx.eval. 학습하는 것에 따라 loop 의 복잡성 scale. 모양 은 안 그래.

Code

End-to-end training loop — synthetic regression 의 2-layer MLP·python
import mlx.core as mx
import mlx.nn as nn
import mlx.optimizers as optim


class MLP(nn.Module):
    def __init__(self, in_dim=1, hidden=16, out_dim=1):
        super().__init__()
        self.l1 = nn.Linear(in_dim, hidden)
        self.l2 = nn.Linear(hidden, out_dim)

    def __call__(self, x):
        return self.l2(nn.relu(self.l1(x)))


# Synthetic data: y = 2x + 0.5 + small noise
mx.random.seed(42)
N = 256
x = mx.random.uniform(-3, 3, (N, 1))
true_w, true_b = 2.0, 0.5
y = true_w * x + true_b + mx.random.normal((N, 1)) * 0.1

model = MLP()
mx.eval(model.parameters())   # materialize once before the loop

optimizer = optim.SGD(learning_rate=0.05)


def loss_fn(model, x, y):
    return ((model(x) - y) ** 2).mean()


loss_and_grad = nn.value_and_grad(model, loss_fn)

for step in range(200):
    loss_v, grads = loss_and_grad(model, x, y)
    optimizer.update(model, grads)
    mx.eval(model.parameters(), optimizer.state)
    if step % 50 == 0:
        print(f'step {step:3d}  loss {float(loss_v):.5f}')

# Verified output (2026-05-03):
#   step   0  loss 9.29228
#   step  50  loss 0.03690
#   step 100  loss 0.02353
#   step 150  loss 0.01696
#   final loss: 0.013964570127427578

# Quick inference check
xt = mx.array([[1.0]])
print('model(1.0) ≈', float(model(xt).item()), '  (true 2*1+0.5 = 2.5)')
# Verified: model(1.0) ≈ 2.544  — within noise of true target.

External links

Exercise

Training loop 돌려. Loss 떨어지고 마지막 추론이 2.5 에 가깝다 확인. 그 다음 두 가지 변경, 한 번에 하나씩 — (a) hidden 을 64 로 늘려 다시 돌려, (b) mx.eval(model.parameters(), optimizer.state) 줄 빼고 다시 돌려. (a) 에서 loss 가 더 빨리 떨어져? (b) 에서 학습이 여전히 동작해, 그리고 Activity Monitor 에서 메모리 동작이 어떻게 보여? 두 문장 총. (b) 실험이 lesson 4 의 lazy-eval 논의를 촉각으로.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.