마루옵스서버 · 네트워크 · AI 기술 노트

소개문의

[Local LLM 구축하기 26] 적재 시간·첫 응답·생성 속도 따로 측정하기

M.2 SSD 뒷면의 기판

참고 사진 · M.2 SSD 뒷면의 기판 · 사진: 4300streetcar / 원본 / CC BY 4.0 · 목록용 크기 조정

Local LLM 구축하기 · 26/30 · 심화와 운영

전체 연재와 학습 순서 · 실습 코드 ZIP

응답이 느릴 때는 어느 구간에서 기다리는지부터 나눠 보면 좋아요. 모델 파일을 읽는 시간, 입력을 처리하는 시간, 답변을 한 토큰씩 만드는 시간은 원인이 다릅니다. 같은 질문을 반복하며 각 구간을 기록해 보겠습니다.

13편의 함수와 같은 실습 모델을 사용합니다. 모델 적재, 입력 처리, 출력 생성을 나눠 비교합니다.

측정 조건 기록

모델 태그와 ID, 양자화, 문맥 4096, 출력 한도를 고정합니다. 전원 모드와 다른 GPU 작업도 기록해 두세요. 같은 질문의 반복 요청은 프롬프트 캐시의 영향을 받을 수 있습니다. 노트북은 배터리 사용인지 전원 연결 상태인지도 조건에 포함합니다.

ollama ps
ollama stop qwen3:4b-instruct

stop 이후의 첫 요청에는 모델 적재 시간이 들어갈 수 있습니다. 뒤의 요청은 모델이 메모리에 남은 상태일 수 있어요. 처음 실행할 때와 연속해서 사용할 때를 나눠 기록해야 체감 차이를 설명하기 쉽습니다.

첫 내용과 생성 속도 측정

import json
import time
import urllib.request
from llm_http import BASE, MODEL

payload = {"model": MODEL,
    "messages": [{"role": "user", "content": "백업 보관 정책에서 확인할 항목을 5가지로 설명해 줘."}],
    "stream": True,
    "options": {"num_ctx": 4096, "num_predict": 192, "temperature": 0}}
for run in range(1, 4):
    req = urllib.request.Request(BASE + "/api/chat",
        data=json.dumps(payload, ensure_ascii=False).encode("utf-8"),
        headers={"Content-Type": "application/json"})
    start = time.perf_counter()
    first = None
    final = None
    with urllib.request.urlopen(req, timeout=300) as response:
        for line in response:
            if not line.strip():
                continue
            item = json.loads(line)
            if item.get("message", {}).get("content") and first is None:
                first = time.perf_counter() - start
            if item.get("done"):
                final = item
    elapsed = time.perf_counter() - start
    if not final:
        raise RuntimeError("Missing final stream message")
    seconds = final.get("eval_duration", 0) / 1e9
    print(json.dumps({"run": run, "first_content_seconds": first,
        "wall_seconds": round(elapsed, 3),
        "load_seconds": final.get("load_duration", 0) / 1e9,
        "prompt_tokens": final.get("prompt_eval_count"),
        "output_tokens": final.get("eval_count"),
        "decode_tokens_per_second": final.get("eval_count", 0) / seconds if seconds else None},
        ensure_ascii=False))
.\.venv\Scripts\python.exe .\benchmark.py

first_content_seconds는 클라이언트가 첫 내용 조각을 받은 시간입니다. 헤더 도착 시각이나 서버 내부의 첫 토큰 계산 시각과는 다릅니다. decode_tokens_per_second는 서버가 보고한 생성 토큰 수를 생성 시간으로 나눈 값이며 전체 요청 처리량과 구분합니다.

설정 한 가지씩 비교

증상먼저 할 실험
첫 요청만 느림load_duration와 모델 상주 시간 비교
긴 문서에서 첫 출력이 늦음입력 길이를 반으로 줄여 prefill 비교
출력 내내 느림GPU 적재 비율·메모리 대역폭 점검
전체 PC가 버벅임RAM 압박·페이지 파일·동시 앱 확인
여러 사용자에서 악화동시 요청을 한 개로 제한해 비교

문맥, 모델 크기, 양자화, 동시성을 한꺼번에 바꾸지 않습니다. 속도를 높인 설정으로 18편의 평가도 다시 해 보세요. 숫자나 형식 오류가 늘었다면 품질을 유지한 채 빨라졌다고 말하기 어렵습니다.

증상별 로그 확인

Windows 로그 위치는 공식 문서의 %LOCALAPPDATA%\Ollama 안내를 따릅니다. 연결 오류는 서버 실행부터, 모델 오류는 태그와 다운로드부터 확인합니다. 메모리 오류는 문맥과 상주 모델, GPU 지원을 살펴봅니다. 로그의 키나 대화 원문은 공개하지 않습니다.

예제에서 얻은 세 줄의 결과와 실행 조건을 함께 보관합니다. 반복 측정할 수 있다면 중앙값과 느렸던 구간도 비교해 보세요. 개인 화면의 빠른 첫 응답과 공유 서버의 많은 요청 처리는 목표가 다를 수 있습니다.

참고 자료

문서·모델 태그 확인 기준: 2026년 10월 18일. 기본 실습 환경: Windows 11 x64.


댓글

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

닉네임·이메일·댓글 내용·IP 주소·브라우저 정보는 댓글 게시 및 관리에 사용됩니다. 이메일과 접속 정보는 공개 댓글에 표시하지 않습니다. 개인정보 요청은 운영자 이메일로 보내 주세요. 개인정보처리방침