본문 바로가기
C.W.K.
Stream
Lesson 03 of 07 · published

NumPy 습관은 거의 통한다 — 딱 두 곳만 빼고

~14 min · numpy, operations, reshape, broadcasting

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

익숙한 것의 90%는 그대로 써

NumPy를 손에 익혔다면 대부분은 의식하지도 못한 채 MLX로 옮겨와. 정수 색인, 콜론 슬라이싱, 모양이 다른 배열의 브로드캐스팅, reshape, sum·mean·argmax 같은 축약 연산이 모두 예상대로 동작해. NumPy에서 쓰던 식을 그대로 입력해도 놀랄 만큼 많은 MLX 코드가 맞아.

이 레슨에서는 그대로 이어지는 부분을 확인하고, 미리 모르면 발을 걸 딱 두 가지 차이만 이름 붙일 거야.

그대로 이어지는 것들

색인과 슬라이싱, 불리언 마스크는 부분 배열을 돌려줘. 브로드캐스팅과 축약 연산은 축을 지정하든 안 하든 동작하고, reshape도 같아. 항목 대입으로 제자리 수정도 가능해. 오래된 MLX 문서에는 다르게 적힌 경우가 있지만 0.31.x에서는 a[i, j] = value가 잘 동작해. 나도 이건 조금 놀랐어.

외워둘 차이 두 가지

1. 기본이 지연 실행이야. NumPy에서 y = x * 2 + 1을 실행하면 곧바로 값을 계산해 저장해. MLX에서는 계산 방법을 담은 그래프 노드를 만들 뿐이야. 배열을 출력하거나 스칼라에 .item()을 호출하거나 Python 목록으로 바꾸는 등 실제 값을 요구할 때 계산이 시작돼. 다음 레슨에서 자세히 다룰 거야. 지금은 배열의 생김새는 NumPy와 비슷해도 계산 시점은 다르다는 것만 잡아.

2. 난수 API의 인자 모양이 달라. NumPy의 np.random.randn(1024, 1024)는 각 크기를 위치 인자로 넘겨. MLX의 mx.random.normal((1024, 1024))은 모양을 튜플 하나로 넘겨야 해. uniform, randint도 같아. 튜플을 빼먹으면 "모양은 튜플이어야 한다"는 친절한 설명 대신 인자 수가 틀렸다는 애매한 오류를 만나.

표시해둘 건 정말 이 둘뿐이야

나머지는 대부분 NumPy 습관을 믿어도 돼. NumPy 코드처럼 느껴지는 식을 쓰고 있다면 대개 올바른 MLX 코드야. 막히는 지점은 거의 언제나 지연 실행의 시점이나 난수 API의 모양 튜플이야. 둘 중 하나가 물면 이 레슨으로 돌아와.

Code

그대로 이어지는 것들 확인하기·python
import mlx.core as mx

a = mx.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])
print('a       :', a)
print('a[0]    :', a[0])
print('a[:, 1] :', a[:, 1])
print('a.sum() :', a.sum())
print('a.sum(axis=0):', a.sum(axis=0))
print('a.mean():', a.mean())
print('a.argmax(axis=1):', a.argmax(axis=1))

# Broadcasting (shape (3,) broadcasts against shape (2, 3))
b = mx.array([10.0, 20.0, 30.0])
print('a + b   :', a + b)

# Reshape
print('a.reshape(3,2):', a.reshape(3, 2))

# In-place via item assignment — works fine in mlx 0.31.x
a[0, 0] = 99.0
print('after a[0,0]=99 :', a)
차이 1 — 기본은 지연 실행, 자세한 내용은 레슨 4·python
import mlx.core as mx

x = mx.array([1.0, 2.0, 3.0])
y = x * 2 + 1                  # NumPy would compute now; MLX records a graph
# At this point y is a graph node, not concrete numbers.
print('y :', y)                 # ← print() triggers implicit eval; you see [3, 5, 7]

# To force computation explicitly, without a print:
big = mx.random.normal((1024, 1024))
big_squared = big @ big.T       # lazy — no work done yet
mx.eval(big_squared)            # NOW the kernel runs
print('big_squared.sum():', float(big_squared.sum()))
차이 2 — 난수 API는 모양 튜플을 받아·python
import mlx.core as mx

# NumPy:  np.random.randn(3, 4)
# MLX:    mx.random.normal((3, 4))    — shape is a single tuple argument

x = mx.random.normal((3, 4))
print('normal(3,4)  shape:', x.shape, 'dtype:', x.dtype)

u = mx.random.uniform(low=0.0, high=1.0, shape=(2, 3))
print('uniform(2,3) shape:', u.shape)

# Reproducibility: seed once at the top of your script
mx.random.seed(42)
print('seeded sample:', mx.random.normal((3,)))

External links

Exercise

예전에 쓴 열 줄 안팎의 NumPy 코드를 하나 골라 npmx로 바꾸며 MLX 코드로 옮겨봐. 막혔다면 지연 실행이나 난수 모양 튜플 때문인지 확인해. 옮기는 데 걸린 시간과, 막힌 곳 가운데 두 차이가 원인이었던 횟수를 적어. 뜻밖이었던 점도 두 문장으로 남겨.

Progress

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

댓글 0

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

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