한 문단의 shift
NumPy 와 PyTorch 의 eager 모드에선, array 코드의 모든 줄이 그때 작업을 해. y = x * 2 + 1 가 결과 계산하고 저장. y 절대 안 쓰면 작업 낭비. MLX 에선 그 같은 줄이 계산을 묘사하는 작은 graph 를 빌드. 작업은 나중에 일어나 — 네가 (명시적 또는 implicit) 값을 요청할 때.
그게 shift 전부야. MLX 의 lazy evaluation 의 다른 모든 건 이 한 결정의 결과.
실제 계산을 트리거하는 것
Explicit: 하나 이상의 array 에 mx.eval(...) — materialize 의 canonical 방법.
Implicit: 구체적 값 읽어야 하는 무엇이든. print(array) 가 eval 트리거 (그래서 graph 아니라 숫자 봐). Scalar 에 .item() 이 eval 트리거. NumPy array 또는 Python list 로 변환이 eval 트리거. Array 에 indexing 도 eval 트리거 — indexing 은 값을 알아야 하니까.
그래서 REPL 에서 작은 예제 만질 때 MLX 가 NumPy 처럼 느껴지는 거야 — 모든 print 가 조용히 작업을 강제하니까, laziness 가 안 보여. 많은 op 을 함께 묶고 더 적은 GPU dispatch 로 fuse 되길 원할 때만 중요해져.
왜 MLX 가 이걸 굳이 하나
- Kernel fusion — MLX 가 op graph 보면 가끔 그것들을 단일 Metal kernel 로 fuse 가능.
mx.compile(lesson 7) 이 이것에 기댐. Eager 모드는 못 해. - Function transform —
mx.grad,mx.vmap깨끗하게 동작 — side-effect 호출 시퀀스가 아니라 전체 함수를 graph 로 받으니까. JAX 가 같은 trade 해. - 메모리 효율 — 절대 안 읽히는 intermediate 는 통째로 skip 가능.
디버깅 함의
Print 할 때 eval 이 implicit 이니까, NumPy 의 "step-through" 직관이 REPL 에서 여전히 통해 — 물어보면 숫자 보일 거. 함정은 print 없는 production 코드가 네가 예상하는 것보다 훨씬 긴 시퀀스에 걸쳐 lazy 유지할 수 있다는 것 — 에러가 체인 끝의 mx.eval 까지 안 드러나. 두 디버깅 관용구에 익숙해져 — triage 중 의심 intermediate 에 mx.eval 뿌려, 그리고 REPL 에선 같은 효과로 print 뿌려.