~15 min · lab, m3, ladder, prefill, decode, measured
Level 0스펙 시트 훑는 사람
0 XP0/91 lessons0/19 achievements
0/100 XP to next level100 XP to go0% complete
"마이크로아키텍처 하나, 크기 셋, 집 하나. 다이를 두 배로 해도 토큰이 두 배가 되진 않아. 그리고 두 단계가 다른 만큼 모자라는데, 그게 레슨이야."
왜 한 세대가 실험인가
이 집은 우연히 M3를 세 크기 다 갖고 있어. Air의 기본 칩(GPU 코어 10, 100 GB/s, 24 GB), 2023 MacBook Pro의 Max(코어 40, 400 GB/s, 128 GB), office Studio의 Ultra(코어 80, 819 GB/s, 512 GB). 같은 GPU 코어, 같은 메모리 기술, 같은 macOS 빌드, 같은 MLX. 바뀌는 건 개수뿐이고, 벤더의 표가 묶어 놓은 두 차원에서 바뀌어. 대역폭은 1 : 4 : 8.2, GPU 코어는 1 : 4 : 8. 물리 트랙은 프리필이 연산 단계고 디코드가 대역폭 단계라고 말해. 그게 참이면 프리필은 코어를 따르고 디코드는 대역폭을 따라야 하고, 다른 게 끼어들지 않는 한 둘 다 선형으로 늘어야 해. 사다리가 그 테스트야.
모델(4비트)
GB/토큰
air, M3 · 프리필 / 디코드
pro2023, M3 Max
office, M3 Ultra
증거
Qwen3.5-0.8B
0.42
1,707 / 167.6
4,017 / 416.6
6,312 / 338.2
실측 2026-09-15
Qwen3.5-2B
1.06
774 / 76.8
2,441 / 246.9
3,992 / 257.1
실측
Qwen3.5-4B
2.37
308 / 34.4
1,126 / 121.3
1,748 / 147.6
실측
Qwen3.5-9B
4.47
172 / 19.4
642 / 72.9
1,043 / 95.1
실측
Qwen3.5-27B
14.42
49 / 6.1
195 / 23.4
315 / 32.6
실측
Qwen3.5-35B-A3B
1.66
—(20 GB, 24 GB에선 안 돌림)
909 / 109.9
1,276 / 89.1
실측
기본에서 Max로: 선형
코어 네 배, 대역폭 네 배. 9B에서 프리필 3.7배, 디코드 3.8배. 27B에서 프리필 4.0배, 디코드 3.8배. 0.8B에선 디코드 2.5배. 고정 비용의 몫이야. Max의 낮은 토큰당 오버헤드(Air의 1.45에 대해 1.12 ms)도 토큰당 3밀리초에선 다 못 숨겨. 바쁜 노트북의 잡음 안에서 Max는 Air 넷이고, 물리 트랙의 두 단계는 벤더가 그럴 거라고 말한 두 가지에 따라 늘어.
Max에서 Ultra로: 두 번째 다이의 절반
다이 둘, 그러니 종이 위에선 코어 두 배, 대역폭 두 배(2.05배). 9B 실측: 프리필 1.62배, 디코드 1.30배. 27B: 프리필 1.62배, 디코드 1.39배. 혼합 모델: 프리필 1.40배, 디코드 0.81배. 더 느려. 두 번째 다이는 적힌 프리필의 60퍼센트, 적힌 디코드의 30에서 40을 사 주고, 작은 커널이 많은 모델엔 배치 1에서 아무것도 못 사 줘. 앞 레슨의 맞춤이 숫자 둘로 왜인지 말해. Ultra의 디코드 커널은 Max의 87%에 대해 스펙의 61%를 끌어오고, 토큰당 고정 비용은 1.12에 대해 1.67 ms. 연산인 프리필은 덜 잃어. 코어 80개가 전부 바쁠 수 있는 단계니까. M2 대 M3 Ultra 레슨이 세대 사이에서도 찾을 바로 그 패턴이야. 어느 것도 결함이 아니야. 토큰 하나의 규모에서 인터포저가 치르는 값이고, 비교의 양쪽을 다 가진 집에서 쟀어.
사다리를 올라가며 사는 건 실제로 뭔가
셋이고, 서로 다른 것들이야. 기본에서 Max는 실리콘 값에 비례한 속도를 사. Air 넷만큼의 프리필과 디코드. Max에서 Ultra는 속도보다 용량을 사. 4비트 750B급 혼합 모델(450 GB쯤. 저장소의 DeepSeek-V4.1-Flash는 출하 그대로 510 GB)을 담는 512 GB, 어떤 Max도 아예 못 담는 것. 거기에 둘 다 들어가는 모델에서 디코드 3분의 1 더, 프리필 60퍼센트 더. Air는 한편 27B를 작업 집합 안에서 초당 6.1토큰으로 돌렸어. 실효 88 GB/s로, 사다리의 맞춤값 89에 딱 붙어서. 사다리의 바닥은 느린 거지 망가진 게 아니야. 이 레슨의 네 카드 행은 네가 가진 티어와 다음 티어에서 얻을 비율이야. 스펙 시트가 아니라 표에서 읽어.
Code
lab_ladder.py — M3 사다리를 표 하나로, 기본 칩 대비 비율과 함께·python
#!/usr/bin/env python3
"""The M3 ladder, one table: base (air) / Max (pro2023) / Ultra (office), plus the
M2 Ultra (music) for the next lessons. Prefill and decode per model, and each
number's ratio to the base M3 -- bandwidth ratio is 1 : 4 : 8.2, GPU cores 1 : 4 : 8.
Standard library only. Usage: lab_ladder.py air.jsonl pro2023.jsonl office.jsonl music.jsonl"""
import json, sys
machines = []
for path in sys.argv[1:]:
rows = {}
for line in open(path):
r = json.loads(line)
if r.get("experiment") == "ladder":
rows[r["model"].split("/")[-1]] = r
first = next(iter(rows.values()))
machines.append((first["alias"], first["device"], first["spec_gbps"], rows))
order = ["Qwen3.5-0.8B-4bit", "Qwen3.5-2B-4bit", "Qwen3.5-4B-4bit", "Qwen3.5-9B-4bit", "Qwen3.5-27B-4bit", "Qwen3.5-35B-A3B-4bit"]
base = machines[0][3]
print("model GB/tok |" + "".join(f" {m[0]:>9} {m[1].replace('Apple ',''):<9} |" for m in machines))
print(" |" + "".join(f" {'prefill':>9} {'decode':<9} |" for _ in machines))
for name in order:
line = f"{name:21} {base[name]['weight_bytes_per_token']/1e9:6.2f} |" if name in base else f"{name:21} {'':6} |"
for alias, dev, spec, rows in machines:
r = rows.get(name)
line += f" {r['prompt_tps']:9.0f} {r['generation_tps']:<9.1f} |" if r else f" {'-':>9} {'-':<9} |"
print(line)
print("\nratios to the base M3 (air), 9B row:")
r0 = base["Qwen3.5-9B-4bit"]
for alias, dev, spec, rows in machines:
r = rows["Qwen3.5-9B-4bit"]
print(f" {alias:8} spec BW x{spec/100:4.1f} prefill x{r['prompt_tps']/r0['prompt_tps']:4.1f} decode x{r['generation_tps']/r0['generation_tps']:4.1f}")
# 2026-09-15, mlx 0.32.2 / mlx-lm 0.31.3, prompt 209 tokens, 200 generated, greedy, batch 1:
# model GB/tok | air M3 | pro2023 M3 Max | office M3 Ultra | music M2 Ultra |
# | prefill decode | prefill decode | prefill decode | prefill decode |
# Qwen3.5-0.8B-4bit 0.42 | 1707 167.6 | 4017 416.6 | 6312 338.2 | 5396 369.4 |
# Qwen3.5-2B-4bit 1.06 | 774 76.8 | 2441 246.9 | 3992 257.1 | 3055 275.8 |
# Qwen3.5-4B-4bit 2.37 | 308 34.4 | 1126 121.3 | 1748 147.6 | 1333 153.3 |
# Qwen3.5-9B-4bit 4.47 | 172 19.4 | 642 72.9 | 1043 95.1 | 789 105.5 |
# Qwen3.5-27B-4bit 14.42 | 49 6.1 | 195 23.4 | 315 32.6 | 233 35.3 |
# Qwen3.5-35B-A3B-4bit | - - | 909 109.9 | 1276 89.1 | 986 101.5 |
# ratios to the base M3, 9B row: pro2023 BW x4.0 prefill x3.7 decode x3.8; office BW x8.2 prefill x6.1 decode x4.9; music BW x8.0 prefill x4.6 decode x5.5
닿을 수 있는 한 세대의 모든 맥에서, 아니면 네 맥 하나에 친구 것 하나를 더해 사다리를 돌리고, lab_ladder.py로 표를 만들어. 9B(또는 둘 다 담는 가장 큰 모델)의 프리필과 디코드 비율을 스펙 비율 옆에 카드에 적어. 그다음 답해. 어느 티어 경계가 선형으로 늘었고 어느 게 아니었는지, 아닌 쪽에선 어느 단계가 더 잃었는지.
Hint
기본에서 Max는 거의 선형이고 인터포저를 건너는 건 뭐든 모자랄 거라고, 프리필보다 디코드가 더 모자랄 거라고 예상해. 어떤 경계가 선형보다 잘 늘면 한 기계가 열로 스로틀됐거나 바빴던 거야. 느린 쪽을 다시 돌려.
Progress
Progress is local-only — sign in to sync across devices.