3줄 요약
kimi-k3-mlx는 Moonshot의 2.78조 파라미터 네이티브 멀티모달 MoE 모델 Kimi K3를 Apple Silicon용 MLX 프레임워크로 옮긴 포트다. 텍스트 타워, 비전 타워, 멀티모달 래퍼, 스트리밍 변환기, 39개 테스트가 한 리포에 들어 있다.- 그런데 원본 전문가를 그대로 담은 양자화 티어(mxfp4부터 2bit까지)는 어떤 맥에서도 돌지 않는다. 가장 작은 것도 869GB인데 현존하는 가장 큰 Apple Silicon이 512GB라, 저자 말대로 “생략한 절차가 아니라 산술적으로 불가능"하다. 실제로 512GB에 앉아 돌아가는 건 전문가를 대거 쳐낸 REAP 프루닝 티어(451·350GB)뿐이고, 그마저 0.14~0.20 tok/s라 대화용은 못 된다.
- 그럼에도 검증은 진짜다. mxfp4 티어는 원본과 비트 단위로 동일하고, 비전 타워는 파이토치 레퍼런스와 1.5e-6 상대오차까지 일치하며, 전문가(expert)가 언어·코드 도메인별로 뭉친다는 측정 결과까지 담겨 있다.
무엇을 포팅했나
원본은 moonshotai/Kimi-K3다. 총 2.78조 파라미터에 토큰당 활성 파라미터는 1040억(104B)인 native-multimodal MoE로, Kimi Delta Attention(KDA)과 Attention Residuals(AttnRes) 같은 K3 고유의 어텐션 설계 위에 세워졌고, 컨텍스트 길이는 100만 토큰이다.
포트는 네 조각으로 나뉜다.
kimi_k3.py: 텍스트 타워. stockmlx-lm과 호환되는 단일 모델 정의.kimi_k3_vision.py: 비전 타워와 프로젝터.kimi_k3_vl/: 둘을 잇는 mlx-vlm 래퍼.scripts/convert.py: 스트리밍 변환기. K3는 bf16(가중치 하나를 16비트로 저장하는 형식)으로 5.6TB라 모델 전체를 메모리에 올리는mlx_lm convert를 쓸 수 없어 한 레이어씩 흘려보내며 변환한다.
아키텍처 — 새로 구현해야 했던 다섯 가지
모델의 골격부터 정리하면 이렇다.
| 요소 | 내용 |
|---|---|
| MoE | 896개 routed expert, top-16 선택, 토큰마다 항상 켜지는 shared expert 2개 |
| Stable LatentMoE | routed expert가 7168차원 residual이 아니라 3584차원 latent 공간에서 동작 |
| 어텐션 | 93개 레이어: KDA 69개 + gated MLA 24개, 96 heads, head_dim=128 |
| 활성화 | SiTU-GLU, 상수 β=4, β_lin=25 |
| 어휘 | 163,840 · tiktoken |
MoE(Mixture of Experts, 전문가 혼합)는 모델을 하나의 큰 신경망 대신 수백 개의 작은 신경망(expert)으로 나눠 두고, 토큰마다 그중 일부만 골라 켜는 구조다. K3는 896개의 routed expert 중 라우터가 토큰마다 16개(top-16)만 활성화하고, 여기에 토큰과 상관없이 늘 켜지는 shared expert 2개를 더한다. 그래서 전체 파라미터가 2.78조로 거대해도 토큰 하나를 처리할 때 실제로 도는 몫은 1040억뿐이다.

파라미터의 97.94%가 routed expert다. 총 2780.0B 중 2722.7B가 여기에 몰려 있다. 이 회계가 맞다는 근거는 예측한 리포 크기(1.561TB)가 실제 크기(1.561TB)와 정확히 일치한다는 점이다.
저자는 K3에만 있고 MLX 어디에도 없는 다섯 가지를 새로 구현했다고 밝힌다. 기존 mlx-lm의 kimi_linear.py가 KDA·MLA·희소 MoE는 이미 다루지만, 아래는 전부 신규다. 다섯 항목은 어텐션·활성화 내부를 손본 구현 디테일이라, 얕게 훑고 지나가도 글의 흐름은 이어진다.
- SiTU-GLU 활성화 : tanh 포화점이 멀리 있어 bf16의 반올림 오차가 드러나므로, 더 정밀한 fp32(32비트)로 계산한다.
- AttnRes : 12개 레이어마다 residual을 스택에 쌓고, 각 서브레이어 입력을 그 스택에 대한 softmax 혼합으로 만든다.
- LatentMoE : routed expert는 레이어를 관통하는 7168차원 메인 벡터(residual)를 그보다 작은 3584차원으로 눌러 그 안에서 계산한다. shared expert는 그러지 않는다.
- q-LoRA + 출력 게이트 MLA : Kimi-Linear에는 평범한
q_proj만 있고 게이트가 없었다. - 채널별 KDA 감쇠 : 레퍼런스 코드는
A_log를[num_heads]=[96]으로 할당하지만, 실제 배포된 모든 shard는[128]=head_dim으로 나온다. 즉 감쇠는 헤드가 아니라 채널마다 걸리며 헤드 간에 공유된다. 레퍼런스 초기화 코드가 배포 가중치보다 낡았고, 권위를 갖는 쪽은 shape다.
마지막 항목이 특히 인상적이다. 레퍼런스 파이썬 코드를 그대로 믿고 Kimi-Linear 레이아웃을 가정하면 “조용한 쓰레기값"이 나온다. 크래시도 에러도 없이 그냥 틀린 값이 나오기 때문에, 테스트가 이 shape를 직접 검증한다.
양자화 티어 — 원본이 이미 MXFP4다
양자화(quantization)는 가중치 하나를 나타내는 비트 수를 줄여 모델 용량을 압축하는 기법이다. 16비트를 4비트·2비트로 낮추는 식이고, 그 지표가 bpw(bits per weight, 가중치당 비트 수)다. 비트를 줄일수록 작아지지만 원본과의 오차는 커진다.
티어 목록 전체를 지배하는 사실이 하나 있다. 원본의 expert 가중치가 이미 MXFP4, 곧 4비트짜리 실제 정보라는 것이다.

MLX에는 원본과 완전히 동일한 인코딩(low-nibble-first 코드, e8m0 스케일)을 쓰는 네이티브 mxfp4 모드가 있다. 그래서 원본 바이트를 산술 연산 없이 그대로 재해석해 MLX로 옮길 수 있고, 이 mxfp4 티어는 end-to-end로 오차 0이 검증된 비트 단위 일치다. 반대로 같은 가중치를 affine 4비트로 재양자화하면 평균 상대오차 9.8%가 생기고 크기도 오히려 더 커진다(가중치당 4.5비트 대 4.25비트. 4비트 코드에 그룹마다 스케일 값이 조금 얹혀 정확히 4가 아니다). 그래서 평범한 4bit 티어는 만들 이유가 없어 아예 빼버렸다.
| 티어 | bpw | 크기 | 원본 대비 expert 오차 |
|---|---|---|---|
| mxfp4 | 4.25 | ~1.48TB (+bf16 나머지 114GB = 1.56TB) | 0, 비트 단위 일치 |
| 3bit | 3.50 | ~1.22TB | 손실 있음 (2차 양자화) |
| mixed2 | ~2.9 | ~1.02TB | expert 2비트, 나머지 4비트 |
| 2bit | 2.50 | ~0.87TB | 손실 있음 |
6bit·8bit·bf16은 만들지 않는다. 4비트 값을 위로 부풀려 저장하는 셈이라 원본보다 크기만 커지고 품질 이득은 0이기 때문이다.
비전 타워는 실제로 검증됐다
텍스트 타워와 달리 비전 타워는 크기가 작아서 진짜로 검증할 수 있다. MoonViT는 이미지를 작은 격자 조각(patch)으로 잘라 처리하는 비전 트랜스포머다. 여기서는 3D(비디오 지원) 버전으로 27개 레이어, 4억4700만(447M) 파라미터에, patch를 묶어 토큰 수를 줄이는 patchmergerv2 프로젝터가 붙는다.
tests/test_vision_parity.py는 Moonshot의 실제 파이토치 코드를 CPU에서 돌려, 전체 27개 레이어를 K3 실제 차원 그대로 MLX 포트와 대조한다. 결과는 end-to-end 1.5e-6 상대오차(float32 반올림 수준)의 일치다.
여기서 판단이 갈린 지점이 하나 있었다. 레퍼런스가 nn.RMSNorm(dim)을 eps 없이 만들면 파이토치는 finfo(dtype).eps로 넘어가는데, 이 값이 fp32에서는 1.19e-7이지만 bf16에서는 7.8e-3으로 정규화 계수가 크게 달라진다. 포트는 upcast 구현에 맞춰 fp32 값을 쓴다. 또 ViT 블록은 tanh 근사 GELU를, 프로젝터는 정확한(erf) GELU를 쓴다. 레퍼런스가 일부러 둘을 섞어 놓았고, 둘은 다른 함수다.
멀티모달 — 269라는 숫자
멀티모달 경로에서 가장 중요한 조각은 “확장 병합(expanding merge)“이다. K3 프로세서는 텍스트 안의 이미지 자리표시자를 다시 써서, 토큰화된 프롬프트에 이미지당 정확히 하나의 <|media_pad|>만 남긴다. 이 한 토큰이 그 이미지의 특징 블록 전체로 부풀어야 한다.
대부분의 LLaVA 계열 모델(이미지를 언어 모델에 이어 붙이는 대표적 오픈소스 비전-언어 방식)은 이렇게 동작하지 않는다. 그쪽은 이미 이미지 토큰마다 자리표시자를 하나씩 찍어두고 같은 길이로 흩뿌리기만 한다. 여기서 그 흩뿌리기를 그대로 적용하면 이미지당 토큰 하나만 남기고 나머지를 조용히 버린다. 크래시도 에러도 없이, 유창한 문장을 쓰면서 사실상 그림 대부분을 못 보는 모델이 된다.
검증 로그가 이 차이를 보여준다.
image (448, 448) -> 1024 patches, grid (1, 32, 32), 256 image tokens
prompt 14 tokens, 1 media_pad -> expands to 269
prefill 269 merged positions in 9.8s (expected 269)
--> The image shows two geometric shapes rendered as
테스트 이미지에는 도형이 정확히 둘(빨간 사각형, 파란 원) 있고, 모델은 “두 개의 기하학적 도형"이라고 보고했다. 처음 보는 이미지에서 개수와 범주를 맞힌 것이다. 여기서 269가 핵심 숫자다. 자리표시자 1개가 이미지 토큰 256개로 부풀어야 하므로, 원래 14개 토큰에서 그 1개를 빼고 256개를 더해 269개가 된다(14 − 1 + 256 = 269). 같은 길이 흩뿌리기였다면 14개 그대로 남아, 256개 대신 1개만 모델에 보여주고도 여전히 유창한 텍스트를 뱉었을 것이다.
실행할 수 없다는 사실
이 리포에서 가장 정직한 대목이다.

이 리포의 어떤 것도 단일 맥에서 돌아가지 않는다. 가장 작은 티어가 ~870GB인데, 현존하는 가장 큰 Apple Silicon 머신의 상한이 512GB다.
여기서 짚어둘 게 있다. 이 절의 “돌지 않는다"는 원본 전문가를 통째로 담은 양자화 티어(mxfp4·3bit·mixed2·2bit) 이야기다. 통합 메모리(unified memory, 맥에서 CPU와 GPU가 함께 쓰는 단일 메모리. 모델 가중치를 통째로 여기 올려야 돌아간다)의 최대치는 M3 Ultra Mac Studio의 512GB이고, 이 티어들 중 가장 작은 것이 869GB다. mxfp4(4.25비트·약 1.56TB)를 512GB에 밀어 넣으려면 가중치당 1.38비트 이하로 줄여야 하는데(512÷1560×4.25 ≈ 1.4), 4비트짜리 정보를 그렇게까지 눌러 담을 방법은 없다. 저자는 결과를 담담하게 나열한다.
- 이 티어들은 한 토큰도 만들어낸 적이 없다. perplexity도, 생성도, 스모크 테스트도 없다. 편법을 쓴 게 아니라 이 하드웨어에서 산술적으로 불가능하다.
- 검증된 것은 따로 있다. mxfp4 티어의 원본 대비 비트 일치, 전체 497,220개 텐서에 대한 체크포인트 키 100% 커버리지, 작은 규모에서의 prefill/decode 일치, 손실 티어의 expert별 코사인 유사도다.
- 2비트 티어들은 Moonshot의 MXFP4 위에 다시 양자화를 얹은 이중 양자화라, 품질은 정말로 미지수이며 나쁠 수도 있다.
돌아가는 길이 아예 없는 것은 아니다. 전문가를 대거 쳐낸 REAP 프루닝 티어는 512GB 맥에 실제로 앉아 토큰을 만든다. 바로 다음 절이다.
REAP 프루닝 — 512GB에 앉히는 유일한 길
프루닝(pruning)은 중요도가 낮은 expert를 아예 잘라내 모델을 작게 만드는 것이다. 512GB 머신에서 K3를 돌아가게 만드는 유일한 경로가 이 REAP 전문가 프루닝이다. Cerebras의 REAP은 각 expert의 중요도(saliency)를 아래 점수로 매긴다.
$$S_j = \frac{1}{|C|} \sum_{x} g_j(x) \cdot \lVert e_j(x) \rVert_2$$$S_j$는 expert $j$의 점수다. 보정용 문장 모음 $C$의 토큰 $x$마다, 라우터가 그 expert에 준 가중치 $g_j(x)$와 그 expert 출력의 크기 $\lVert e_j(x) \rVert_2$를 곱해 평균낸 값이다. 자주, 세게 쓰이는 expert일수록 높다. 레이어별로 이 점수가 높은 expert만 남기고, 라우터가 남은 것들로 확률을 다시 맞춘다(재정규화). 원래는 1.56TB 모델을 통째로 돌려야 할 것 같지만, 레이어 L의 saliency는 L로 들어오는 hidden state와 그 라우터 게이트, expert 출력에만 달려 있고 하류와는 무관하다. 그래서 변환기처럼 한 레이어씩 흘려보내며 계산한다. 최대 RAM은 캘리브레이션(calibration, 실제 데이터를 흘려보내 어느 expert가 중요한지 재는 과정) 규모에 따라 32GB(64k 토큰)에서 58GB(256k 토큰) 정도다.
실제로 발행된 티어는 셋이다. 이름의 숫자는 잘라낸 비율이다. REAP73은 전문가의 73%를 버려 896개 중 242개만, REAP80은 80%를 버려 179개만 남긴 빌드다. q8은 남은 non-expert 가중치를 8비트로 둔다는 표시다.
| 리포 | 크기 | expert | 캘리브레이션 | tok/s |
|---|---|---|---|---|
| Kimi-K3-REAP73-mxfp4-q8 | 451GB | 242/896 | 혼합(11개 소스) | 0.16 |
| Kimi-K3-REAP80-mxfp4-q8 | 350GB | 179/896 | 혼합 | 0.20 |
| Kimi-K3-REAP73-zh-code-mxfp4-q8 | 451GB | 242/896 | 중국어 + 코드 | 0.14 |
이 세 티어는 512GB 맥에 실제로 올라가 토큰을 만든다. 살아남은 expert는 모든 빌드에서 Moonshot MXFP4의 비트 단위 복사본이고, 잃는 정보는 프루닝 그 자체뿐이다. 다만 셋 다 대화용은 아니다. 0.14~0.20 tok/s에 그친다. MoE라 토큰마다 실제로 도는 expert는 16개뿐인데도, 그 16개(약 26GB)에 더해 모든 토큰이 반드시 거치는 어텐션·공유 레이어 같은 non-expert 텐서 61GB를 매번 읽어야 한다. 그래서 토큰 하나에 약 87GB를 읽는다. 이 61GB는 전체 파라미터의 2%에 불과한데도 매 토큰 발목을 잡는 몫이다. 160GiB 여유를 둔 350GB 빌드가 실측 0.20, 451GB 빌드가 0.16이라 성능이 크기에 거의 정비례하니, 이건 할당의 문제가 아니라 대역폭의 벽이고 어떤 프루닝 비율로도 뚫리지 않는다.
전문가는 도메인별로 뭉친다 — 측정된 결과
내가 가장 눈여겨본 대목이다. 캘리브레이션 토큰마다 출처 코퍼스를 태깅하고 언어별 saliency를 한 번에 누적하면, expert들이 도메인별로 뭉치는 모습이 드러난다. 896개에서 top-242를 무작위로 두 번 뽑으면 27%가 겹친다. 이 기준선에 대고 재면 이렇다.
| 쌍 | 겹침 | 기준 대비 |
|---|---|---|
| 파이썬 코드 ↔ 다중언어 코드 | 57.2% | 2.1배 |
| 독일어 ↔ 스페인어 | 59.3% | 2.2배 |
| 독일어 ↔ 영어 웹 | 56.5% | 2.1배 |
| 중국어 ↔ 일본어 | 42.8% | 1.6배 |
| 중국어 ↔ 파이썬 코드 | 17.8% | 0.66배, 기준 이하 |
코드, 유럽어, CJK가 각각 군집을 이루고, 코드와 중국어는 서로 밀어낸다.

이 구조를 겨냥해 도메인별 빌드를 만들면 능력이 실제로 갈린다. 같은 프롬프트를 greedy(매번 확률이 가장 높은 토큰만 고르는 가장 단순한 생성 방식)로 24토큰 생성한 다섯 빌드의 결과다. 유지율은 남긴 expert가 원래 중요도(saliency)의 몇 %를 담는지를 뜻한다.
| 빌드 | 유지율 | 중국어 | 코드 |
|---|---|---|---|
| 혼합 | 59.1% | 괜찮지만 18토큰쯤에서 프롬프트로 되돌아가 반복 | 정상 |
| REAP-80 (179 expert) | ~50% | 강한 반복 루프 | 정상 |
| 영어+코드 | 68.4% | 완전 붕괴 | 정상 |
| 중국어만 | 79.8% | 루프는 없지만 모호 | 완전 붕괴 |
| 중국어+코드 | 69.3% | 가장 좋음, 정확하고 구체적 | 정상 |
한 도메인의 expert를 잘라내면 그 도메인이 죽고, 남기면 나아지며, 두 도메인을 합치면 둘 다 버틴다. expert 집합이 능력을 결정하고, 그 집합은 코퍼스를 고르는 것으로 고른다.
저자는 여기에 정직한 단서를 붙인다. 이건 도메인당 프롬프트 하나, 24토큰, greedy라 엄밀한 평가가 아니다. expert 겹침과 saliency 유지라는 두 독립 측정과 방향이 맞아서 믿을 만할 뿐, 진짜 주장을 하려면 도메인마다 여러 프롬프트와, 보정에 쓰지 않고 따로 떼어둔 평가 데이터(held-out)에서 잰 perplexity(모델이 다음 토큰을 얼마나 잘 맞히는지 재는 지표, 낮을수록 좋음)가 필요하다. 실제로 이 파일의 이전 버전은 같은 실험을 null 결과로 잘못 기록한 적이 있고, 같은 회의를 지금의 긍정 결과에도 적용해야 한다고 스스로 덧붙인다.
검증
39개 테스트가 아키텍처와 497,220개 키 커버리지, 비전 타워 패리티, 자리표시자 확장, 변환기 왕복, 프로세서 통합을 확인한다.
| 스위트 | 검증 대상 | 개수 |
|---|---|---|
test_kimi_k3.py | 아키텍처 + 497,220 키 커버리지 | 13 |
test_vision_parity.py | 비전 타워 대 파이토치 레퍼런스 | 9 |
test_vl_wrapper.py | 자리표시자 확장 + 멀티모달 경로 | 11 |
test_convert_roundtrip.py | 변환기(합성 미니 K3) | 3 |
test_processor_integration.py | 실제 프로세서 → MLX 타워 | 3 |
verify.py는 티어를 로드하지 않고 검사한다. expert별 코사인 유사도(두 벡터가 같은 방향을 가리키는 정도. 1이면 같은 방향, 0이면 무관)를 보고하는데, 양자화 노이즈로는 이 값이 거의 안 움직이지만 잘못 쌓인 expert나 행·열이 뒤바뀐 행렬(전치)은 크기 통계가 그럴듯해 보여도 코사인을 0 근처로 떨어뜨린다.
가장 흥미로운 지점
이 리포가 던지는 질문은 단순하다. 전문가를 하나도 버리지 않은 완전판, 곧 끝내 돌릴 수 없는 그 모델을 이렇게까지 정밀하게 포팅하는 일에 무슨 의미가 있는가.
답은 리포 자체가 하고 있다. 실행 가능성과 정확성은 다른 축이다. mxfp4 티어가 원본과 비트 단위로 같다는 것, 비전 타워가 파이토치와 1.5e-6까지 맞는다는 것, 잘못 매핑된 expert가 코사인 0으로 잡힌다는 것, 이 사실들은 완전판 모델이 단 한 토큰을 생성하지 않고도 참이다. 저자는 완전판을 두고는 “돌려봤더니 되더라"라는 가장 흔한 증거를 손에 넣을 수 없는 상황에서, 그 증거 없이도 옳음을 증명하는 방법을 통째로 구축했다.

그래서 이 포트는 미래를 향한 예약이기도 하다. 언젠가 통합 메모리가 869GB를 넘는 날, 혹은 REAP로 깎아낸 티어가 대역폭의 벽 너머로 갈 날, 이 코드는 이미 준비되어 기다린다. 지금은 산술이 막고 있을 뿐이다.
출처
PipeNetwork, kimi-k3-mlx (GitHub README)
원문: https://github.com/PipeNetwork/kimi-k3-mlx
원본 모델: https://huggingface.co/moonshotai/Kimi-K3
이 다이제스트의 삽화는 원문에 인용할 이미지가 없어, 블로그 글 「느낌적인 느낌을 숫자로 옮기는 일」의 치비 서소영 라인아트를 참조하여 gpt-image-2 image-to-image로 새로 그렸다.
