본문 바로가기
C.W.K.
Lesson 05 of 06 · published

M2 Ultra 대 M3 Ultra: 같은 메모리, 더 새 GPU

~14 min · lab, m2-ultra, m3-ultra, gpu-generation, stream, measured

Level 0스펙 시트 훑는 사람
0 XP0/91 lessons0/19 achievements
0/100 XP to next level100 XP to go0% complete
"Studio 둘, 2년 차이, 같은 800 남짓 GB/s. 새것은 연산인 단계를 이기고 메모리인 단계를 져. 모든 모델에서, 같은 폭으로."

두 번째 통제 실험

M3 사다리는 마이크로아키텍처를 고정하고 개수를 바꿨어. 이 레슨은 메모리를 거의 고정하고 세대를 바꿔. music Studio의 M2 Ultra(GPU 코어 76, 800 GB/s, 192 GB)에 대한 office Studio의 M3 Ultra(코어 80, 819 GB/s, 512 GB). 새 칩에 대한 애플 자체의 주장은 GPU가 "차세대 GPU" 아키텍처(Dynamic Caching, 하드웨어 레이 트레이싱, 메시 셰이딩)로 "M2 Ultra보다 최대 2배 빠른 성능"을 가져오고, UltraFusion이 M3 Max 다이 둘을 "단일 통합 칩으로" 잇는다는 거야. 한편 대역폭 칸은 2퍼센트 움직였어. 그러니 예측은 날카로워. 연산 단계인 프리필은 좋아져야 하고, 대역폭 단계인 디코드는 안 움직여야 해. 같은 실험실, 같은 날, 같은 모델, 같은 환경.

모델(4비트)music, M2 Ultra · 프리필 / 디코드office, M3 Ultra · 프리필 / 디코드프리필 비율디코드 비율증거
Qwen3.5-0.8B5,396 / 369.46,312 / 338.2×1.17×0.92실측 2026-09-15
Qwen3.5-2B3,055 / 275.83,992 / 257.1×1.31×0.93실측
Qwen3.5-4B1,333 / 153.31,748 / 147.6×1.31×0.96실측
Qwen3.5-9B789 / 105.51,043 / 95.1×1.32×0.90실측
Qwen3.5-27B233 / 35.3315 / 32.6×1.36×0.92실측
Qwen3.5-35B-A3B986 / 101.51,276 / 89.1×1.29×0.88실측

예측의 절반이 맞았어

프리필은 모든 모델에서 17에서 36% 좋아졌어. 연산 단계의 진짜 세대 이득이야. 벤더의 렌더링 주장인 2배엔 어림도 없지만, 그건 그래픽에 대한 주장이야. 디코드는 그저 안 좋아진 게 아니야. 뒤로 갔어. 모든 모델에서 4에서 12%, 혼합 모델에서 가장 많이. 오래된 Studio가 27B를 초당 35.3토큰으로 디코드하고 새것은 32.6. 대역폭 레슨의 맞춤이 같은 걸 숫자 둘로 말해. M2 Ultra는 디코드 커널에서 800 중 546 GB/s를 끌어오고, M3 Ultra는 819 중 497. 모델이 아예 없는 스트림 테스트는 세 번째 방식으로 말해. M2 Ultra에서 스펙의 92%, M3 Ultra에서 78%. 도구 셋, 순서 하나.

독립된 증인

실험실은 하루의 런타임 하나니까 두 번째를 찾았어. llama.cpp 커뮤니티의 오래된 애플 실리콘 표, 다른 런타임, 다른 모델 가족, 다른 해. 76코어 M2 Ultra가 7B를 F16으로 초당 41.0토큰으로 디코드하고 80코어 M3 Ultra는 39.8, 프롬프트 처리는 반대 방향으로 1,402 대 1,538. 같은 쌍, 같은 방향, 폭은 더 좁아(F16에서 디코드 3%, 프리필 10%쯤. 이 실험실의 4비트에선 4–12%와 17–36%). 이 실험실을 돌린 적 없는 사람들에게서. 왜 새 메모리 시스템이 스트리밍 읽기에 스펙의 작은 몫을 전달하는지는 사용자 공간에서 알 수 없어. 애플은 다이 간 링크의 연결 수와 대역폭을 문서화하지, 메모리에 묶인 커널 아래의 동작을 문서화하진 않아. 퀘스트는 비율을 보고하고, 일치하는 도구 셋의 이름을 대고, 메커니즘 지어내기는 사양해. 카드엔: 오래된 Ultra를 갖고 거기 들어가는 모델을 돌린다면 디코드는 업그레이드할 이유가 아니야. 용량과 프리필이 이유야.

Code

lab_ultras.py — Studio 둘을 나란히, 모델마다·python
#!/usr/bin/env python3
"""Same memory, newer GPU: the M2 Ultra (music, 800 GB/s, 76 GPU cores) beside the
M3 Ultra (office, 819 GB/s, 80 cores), model by model. Prefill is the compute
stage; decode is the bandwidth stage. Standard library only.
Usage: lab_ultras.py ladder-music.jsonl ladder-office.jsonl"""
import json, sys


def load(path):
    rows = {}
    for line in open(path):
        r = json.loads(line)
        if r.get("experiment") == "ladder":
            rows[r["model"].split("/")[-1]] = r
    return rows


a, b = load(sys.argv[1]), load(sys.argv[2])
ra, rb = next(iter(a.values())), next(iter(b.values()))
print(f"{'model':24} {'GB/tok':>6} | {ra['device']:>14} prefill  decode | {rb['device']:>14} prefill  decode | prefill  decode")
for name in ["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"]:
    x, y = a[name], b[name]
    print(f"{name:24} {x['weight_bytes_per_token']/1e9:6.2f} | {'':14} {x['prompt_tps']:7.0f} {x['generation_tps']:7.1f} | {'':14} {y['prompt_tps']:7.0f} {y['generation_tps']:7.1f} | "
          f"x{y['prompt_tps']/x['prompt_tps']:4.2f}   x{y['generation_tps']/x['generation_tps']:4.2f}")
print(f"\nspec bandwidth ratio {rb['spec_gbps']/ra['spec_gbps']:.2f}   (M3 Ultra / M2 Ultra)")

# 2026-09-15, mlx 0.32.2 / mlx-lm 0.31.3:
# model                    GB/tok | M2 Ultra prefill  decode | M3 Ultra prefill  decode | prefill  decode
# Qwen3.5-0.8B-4bit          0.42 |             5396   369.4 |             6312   338.2 | x1.17   x0.92
# Qwen3.5-2B-4bit            1.06 |             3055   275.8 |             3992   257.1 | x1.31   x0.93
# Qwen3.5-4B-4bit            2.37 |             1333   153.3 |             1748   147.6 | x1.31   x0.96
# Qwen3.5-9B-4bit            4.47 |              789   105.5 |             1043    95.1 | x1.32   x0.90
# Qwen3.5-27B-4bit          14.42 |              233    35.3 |              315    32.6 | x1.36   x0.92
# Qwen3.5-35B-A3B-4bit       1.66 |              986   101.5 |             1276    89.1 | x1.29   x0.88
# spec bandwidth ratio 1.02 (M3 Ultra / M2 Ultra)

External links

Exercise

대역폭이 비슷하고 세대가 다른 맥 둘에 닿을 수 있으면 둘 다 사다리를 돌리고 lab_ultras.py로 나란히 표를 만들어. 못 하면 위에 링크된 커뮤니티 표에서 네 맥의 행을 가져와 F16 디코드를 네 대역폭에서 16비트 7B의 상한과 비교해. 새 세대가 어느 단계를 얼마나 사 줄지, 표가 말하는 대로 카드에 적어.
Hint
대역폭이 비슷한 세대 사이에서 디코드 비율이 1.0 근처인 게 기대되는 결과고, 1.0을 한참 넘는 프리필 비율이 세대 이득이야. 디코드 비율이 1.0에서 멀면 두 기계를 같은 컨텍스트 길이와 양자화로 쟀는지 확인해. 거기서의 불일치는 세대 차이와 똑같이 보여.

Progress

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

댓글 0

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

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