~15 min · llm-physics, context-curve, ttft, decode, benchmark, measured
Level 0스펙 시트 훑는 사람
0 XP0/91 lessons0/19 achievements
0/100 XP to next level100 XP to go0% complete
"의미 있는 벤치마크는 90% 컨텍스트에서의 디코드 초당 토큰과 첫 토큰까지 시간뿐이야. 그리고 아무도 그걸 공개하지 않아." — 아빠, 2026-08-26
빈 컨텍스트 숫자
네가 볼 거의 모든 초당 토큰 수치, 벤더 것이든 리뷰어 것이든 포럼 것이든, 짧은 프롬프트와 빈 캐시에서 잰 거야. 실험 트랙의 사다리도 그런 숫자고, 일부러 그래. 가중치를 분리해 주니까. 하지만 아무도 모델을 그렇게 쓰지 않아. 진짜 세션은 긴 시스템 프롬프트, 문서, 저장소, 대화를 지고 다녀. 쓸모 있는 첫 토큰이 나오기 전에 캐시는 이미 크고, 기계가 쓸 만한지를 정하는 두 숫자는 첫 토큰을 얼마나 기다리느냐(지금까지 것 전부의 프리필)와 답이 시작되면 얼마나 빨리 흘러나오느냐(꽉 찬 캐시로 디코드)야. 집주인의 규칙은 거의 꽉 찬 윈도우에서 둘 다 보고하지 않는 벤치마크는 워크로드를 재는 게 아니라는 거야. 이 레슨이 그걸 재.
곡선, 실측
office에서, 32,768토큰 윈도우, 그 10, 50, 90%에서의 프롬프트, 200토큰 생성, 캐시 대 가중치 비율을 가로지르도록 고른 모델 셋.
모델
컨텍스트
프롬프트 토큰
TTFT
프리필 tok/s
디코드 tok/s
피크 GB
증거
Llama-3.2-1B (순수 GQA, 토큰당 32 KB)
10%
3,254
0.56 s
6,572
343.6
1.63
실측, office, 2026-09-15
50%
16,302
2.54 s
6,562
237.4
1.97
실측
90%
29,406
5.82 s
5,105
208.5
2.38
실측
Qwen3.5-9B (하이브리드, 토큰당 32 KB)
10%
3,233
2.69 s
1,277
94.4
6.77
실측
50%
16,337
13.12 s
1,259
86.3
8.13
실측
90%
29,385
24.90 s
1,187
78.1
9.39
실측
Qwen3.5-27B (하이브리드, 토큰당 64 KB)
10%
3,233
8.96 s
367
32.3
18.21
실측
50%
16,337
45.94 s
357
29.3
20.46
실측
90%
29,385
90.38 s
326
27.5
22.57
실측
두 곡선 읽기
디코드는 10%에서 90%로 가면서 1B는 39%, 9B는 17%, 27B는 15% 떨어져. 앞 레슨은 캐시 대 가중치 비율에서 기울기를 예측했어. (가중치 + 캐시) ÷ 가중치로, 1B는 바이트 2.3배, 9B는 1.21배, 27B는 1.13배. 9B와 27B는 예측에 떨어져(실측 1.21배와 1.17배). 1B는 2.3배에 못 미치는데, 토큰당 1.5밀리초쯤의 고정 오버헤드가 3밀리초짜리 토큰을 희석하기 때문이야. 그러니 방향과-때 원칙에서 나온 집주인의 문장, 대역폭은 기울기를 나눌 뿐 곡선의 모양을 바꾸지 않는다는 말은 정확하고, 이 퀘스트가 보정 하나를 더해. 모양은 캐시 대 가중치 비율이고, 하이브리드 아키텍처는 토큰당 캐시가 같은 순수 트랜스포머보다 훨씬 평평한 모양을 가져.
첫 토큰까지 시간이 세션을 실제로 바꾸는 숫자야. 32K 윈도우의 90%에서 27B는 90초, 9B는 25초. 그리고 이건 한 번짜리 프리필이야. 매 턴 전체 이력을 다시 보내는 채팅 클라이언트는 런타임이 턴 사이에 캐시를 유지하고 늘리지 않는 한 매 턴 그 정도의 청구서를 내. 여기서 물리는 봐주지 않아. 프리필은 연산에 묶이니까 처방은 더 많은 행렬 처리량이고, 애플 M5의 Neural Accelerator가 정확히 그걸 위한 거고, 이 퀘스트가 정확히 측정하지 않은 거야.
Code
context_curve.py — 실험의 곡선 실험, 압축판·python
#!/usr/bin/env python3
"""Decode and TTFT at fractions of a context window. Build a prompt of the
target token count, generate, and record what mlx-lm reports. This is the
`curve` experiment of silicon_lab.py, trimmed to its essentials."""
import sys, time
import mlx.core as mx
from mlx_lm import load, stream_generate
from mlx_lm.sample_utils import make_sampler
repo = sys.argv[1] if len(sys.argv) > 1 else "mlx-community/Qwen3.5-9B-4bit"
window = int(sys.argv[2]) if len(sys.argv) > 2 else 32768
FILLER = "Decode re-reads the weights for every generated token, which is why its ceiling is bandwidth divided by bytes per token. "
QUESTION = "Explain the passage in detail, in at least 400 words."
model, tok = load(repo)
def prompt_of(target: int) -> list[int]:
reps = max(1, target // 24) # ~24 tokens per filler sentence
ids = tok.apply_chat_template([{"role": "user", "content": FILLER * reps + QUESTION}], add_generation_prompt=True)
while len(ids) > target and reps > 1:
reps -= 1
ids = tok.apply_chat_template([{"role": "user", "content": FILLER * reps + QUESTION}], add_generation_prompt=True)
return ids
for frac in (0.1, 0.5, 0.9):
ids = prompt_of(int(window * frac))
mx.reset_peak_memory(); t0 = time.perf_counter(); first = None
for r in stream_generate(model, tok, ids, max_tokens=200, sampler=make_sampler(temp=0.0)):
if first is None:
first = time.perf_counter() - t0
print(f"{frac:.0%} of {window}: prompt {r.prompt_tokens:6d} TTFT {first:6.2f}s prefill {r.prompt_tps:7.1f} tok/s "
f"decode {r.generation_tps:6.2f} tok/s peak {r.peak_memory:5.2f} GB")
# office, mlx 0.32.2, 2026-09-15, Qwen3.5-9B-4bit:
# 10%: prompt 3233 TTFT 2.69s prefill 1277 decode 94.38 peak 6.77
# 50%: prompt 16337 TTFT 13.12s prefill 1259 decode 86.31 peak 8.13
# 90%: prompt 29385 TTFT 24.90s prefill 1187 decode 78.10 peak 9.39
네 맥에서 네가 실제로 쓰는 모델과 윈도우로 context_curve.py를 돌려(작업 집합이 담을 수 있는 윈도우를 골라. 앞 트랙의 피크 표가 안내야). 90% 줄을 정직한 벤치마크로 카드에 추가해. 그다음 네가 잰 디코드 기울기를 앞 레슨 연습에서 예측한 비율과 비교하고, 차이가 있으면 한 문장으로 설명해.
Hint
기울기가 비율 예측보다 완만하면 고정 오버헤드가 희석하고 있거나(작은 모델) 런타임이 캐시로 뭔가 영리한 걸 하는 거야. 더 가파르면 기계가 작업 집합 근처일 수 있고 피크 메모리 칸이 그걸 보여줄 거야. 그건 캐시가 아니라 스왑 절벽이야.
Progress
Progress is local-only — sign in to sync across devices.