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

Local 벤치마크

~18 min · ops, benchmark

Level 0Downloader
0 XP0/41 lessons0/11 achievements
0/120 XP to next level120 XP to go0% complete

뭘 재?

(모델, 하드웨어) 조합 하나당 숫자 셋이면 돼. 더 늘리면 표만 무거워지고 결정은 안 빨라져.

  • Token / second (decode). 간판 숫자야. Streaming이 시원하게 느껴지느냐가 여기서 갈려. 사람이 읽는 속도가 대략 10 tok/s쯤이라, 그 아래로 떨어지면 기다리는 게 눈에 보이기 시작해.
  • 첫 토큰까지 걸리는 시간 (TTFT). 모델 load랑 prompt eval이 다 여기 들어가. 모델이 warm이면 거의 0으로 내려앉고, cold면 수십 초까지 뛰어. 그래서 같은 모델이 어떤 날은 빠르고 어떤 날은 느리게 느껴지는 거야 — decode 속도가 아니라 이 숫자가 흔들린 거지.
  • 메모리 비용. ollama psSIZE 컬럼. 이게 결국 같은 머신에서 뭘 동시에 띄울 수 있느냐를 정해.

기억을 믿지 말고 적어

6개월 뒤에 이 Mac에서 32B 모델이 쓸 만했는지 절대 기억 못 해. 하드웨어, 모델, quant, num_ctx, env var, 그리고 숫자 셋. 이걸 적어둬. Markdown 표 하나면 충분해. 이 표가 쌓이면 다음에 하드웨어를 바꿀 때 카탈로그 스펙 대신 네 실측을 놓고 고를 수 있어.

공정하게 재는 법

  • 모델을 이미 warm하게 만들어두고 재. 시간 재기 전에 warmup prompt를 한 번 흘려보내는 거야. 안 그러면 model load 시간이 decode 숫자에 섞여 들어가.
  • 응답 길이가 들쭉날쭉하지 않게 seed를 고정해. 길이가 흔들리면 tok/s도 같이 흔들려.
  • 여러 run에 같은 prompt를 써. prompt가 바뀌면 prompt eval 양이 바뀌고, 그럼 TTFT 비교가 의미를 잃어.
  • 세 번 돌리고 median을 써. 아래 callout이 그 이유야.

Code

벤치마크 harness·python
import httpx, json, time, statistics

OLLAMA = "http://localhost:11434"

def bench(model: str, prompt: str, runs: int = 3) -> dict:
    """모델 warm한 다음 N번 timed iteration."""
    # Warmup
    httpx.post(f"{OLLAMA}/api/chat", json={
        "model": model,
        "messages": [{"role": "user", "content": "ok"}],
        "stream": False, "options": {"num_predict": 1, "seed": 1},
    }, timeout=300.0)

    tps_runs, ttft_runs = [], []
    for i in range(runs):
        t0 = time.time()
        first_token_at = None
        with httpx.stream("POST", f"{OLLAMA}/api/chat", json={
            "model": model,
            "messages": [{"role": "user", "content": prompt}],
            "stream": True, "options": {"seed": 42, "num_predict": 200},
        }, timeout=None) as r:
            for line in r.iter_lines():
                if not line:
                    continue
                chunk = json.loads(line)
                if first_token_at is None and chunk.get("message", {}).get("content"):
                    first_token_at = time.time() - t0
                if chunk.get("done"):
                    ec = chunk.get("eval_count", 0)
                    ed = chunk.get("eval_duration", 1) or 1
                    tps_runs.append(ec / (ed / 1e9))
                    break
        ttft_runs.append(first_token_at or 0)

    return {
        "model": model,
        "runs": runs,
        "tps_median": statistics.median(tps_runs),
        "ttft_median_s": statistics.median(ttft_runs),
    }

print(bench("qwen2.5:7b", "Explain unified memory in 4 bullets."))

External links

Exercise

설치된 모델 셋에 같은 고정 prompt로 벤치마크를 돌려. 모델당 숫자 셋을 Markdown 표에 적어. 네 하드웨어에서 제일 잘 나오는 tok/s를 찾고, 그게 실제 use case에 충분한지 판단해.

Progress

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

댓글 0

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

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