13년차의 서버실

서버, 인프라, 홈랩 기술 블로그

[태그:] Modelfile

  • [AI] Ollama 커스텀 LLM 가이드: Modelfile 사용법과 실전 구축

    [AI] Ollama 커스텀 LLM 가이드: Modelfile 사용법과 실전 구축

    Ollama 커스텀 LLM 가이드: Modelfile 사용법과 실전 구축

    Ollama 커스텀 LLM을 오래 만지다 보면, 결국 성능 숫자보다 출력 습관을 통제하고 싶다는 요구가 더 자주 생기더라고요. 답변은 꼭 한국어로 나오게 하고 싶다든지, 명령어를 먼저 보여주고 설명은 뒤로 보내고 싶다든지, 모르면 지어내지 말고 확인이 필요하다고 말하게 만들고 싶을 때가 그렇습니다. 이럴 때 가장 먼저 손대기 좋은 게 Modelfile입니다.

    다만 기대치는 정확히 잡아야 합니다. Modelfile은 보통 사람들이 떠올리는 “모델 재학습” 자체와는 다릅니다. 실무 감각으로 말하면, 베이스 모델의 가중치를 새로 만드는 도구라기보다 실행 규약을 모델 단위로 고정하는 계층에 가깝습니다. 이 차이를 이해해 두면 왜 어떤 문제는 SYSTEM 한 줄로 풀리고, 어떤 문제는 ADAPTER나 RAG까지 가야 하는지 훨씬 빨리 감이 옵니다.

    이번 글은 로컬 AI 모델 개발 관점에서, Ollama의 Modelfile로 어디까지 바꿀 수 있는지, 어디서 멈추는 게 맞는지, 그리고 실제로 자주 깨지는 지점이 무엇인지까지 한 번에 정리한 글입니다. 문법과 API 필드는 Ollama 공식 Modelfile 문서, Create API 문서, Generate API 문서 기준으로 다시 확인했습니다.

    로컬 환경에서 베이스 모델 위에 Modelfile을 얹어 커스텀 모델을 만드는 전체 흐름입니다.

    1. Ollama 커스텀 LLM이 실무에서 먹히는 이유는 재현성 때문입니다

    제가 Modelfile을 계속 쓰는 이유는 단순합니다. 사람은 매번 같은 프롬프트를 정교하게 붙이기 어렵지만, 파일은 늘 같은 방식으로 동작하거든요. 특히 팀 단위로 로컬 모델을 쓰기 시작하면 모델 품질보다 먼저 문제 되는 게 사람마다 다른 사용 습관입니다. 어떤 분은 시스템 프롬프트를 길게 넣고, 어떤 분은 짧게 넣고, 어떤 분은 아예 안 넣습니다.

    이때 Modelfile은 계약서를 하나 만들어 줍니다. SYSTEM, TEMPLATE, PARAMETER, MESSAGE, ADAPTER를 모델 이름 뒤에 고정해 두니, 같은 이름을 호출하는 한 최소한의 행동 일관성은 확보됩니다. 이거 진짜 편하더라고요. 문서 초안 작성, 운영 가이드 보조, 코드 리뷰 보조처럼 반복성이 높은 작업일수록 차이가 분명합니다.

    • 반복 프롬프트 제거: 매번 붙이던 역할 설명과 금지 규칙을 모델 단위로 고정합니다.
    • 출력 형식 표준화: “명령어 먼저, 설명 나중” 같은 우선순위를 팀 공통 규약으로 만들기 좋습니다.
    • 실험 버전 분리: 같은 베이스 모델에서 보수형, 설명형, 요약형 모델을 나눠 비교하기 쉽습니다.
    • 장애 원인 분리: 모델 자체 문제인지, 템플릿 문제인지, 파라미터 문제인지 추적이 빨라집니다.

    2. Ollama 커스텀 LLM에서 먼저 선을 그어야 합니다: Modelfile이 바꾸는 것과 못 바꾸는 것

    여기서 선을 분명히 그어야 삽질이 줄어듭니다. Modelfile은 행동 규칙, 입력 형식, 실행 파라미터, 예시 대화, 어댑터 연결은 잘 다룹니다. 반면 모델 내부 지식 자체를 새로 학습시키는 일은 기본적으로 하지 못합니다. 문서 몇 줄 넣었다고 도메인 지식을 완전히 새로 습득하는 구조는 아니라는 뜻입니다.

    실제로는 이렇게 나누면 편합니다. “답변의 순서나 톤이 문제냐”면 Modelfile 쪽입니다. “모델이 특정 도메인 지식을 계속 모르거나 아는 척 틀리느냐”면 ADAPTER, RAG, 또는 더 맞는 베이스 모델을 검토하는 편이 맞습니다.

    문제 유형 우선 선택 이유 피해야 할 접근
    답변 톤이 들쭉날쭉함 SYSTEM + MESSAGE 행동 규칙과 말투는 프롬프트 계층에서 통제가 잘 됩니다 바로 ADAPTER부터 붙이기
    출력 형식이 자꾸 깨짐 TEMPLATE + stop 재검토 채팅 포맷 불일치일 가능성이 큽니다 모델 성능 탓으로 돌리기
    응답이 너무 산만하거나 창의성이 과함 PARAMETER 조정 temperature, top_p, num_ctx 영향이 큽니다 SYSTEM 문구만 계속 길게 늘리기
    특정 도메인 지식을 안정적으로 못 씀 ADAPTER 또는 RAG 검토 행동 지시만으로는 한계가 분명합니다 MESSAGE 예시를 과도하게 누적하기
    팀원마다 결과가 다름 Modelfile로 모델 이름 표준화 입력 규약 자체를 버전 관리할 수 있습니다 사람마다 프롬프트 템플릿 수동 복붙

    3. Modelfile 핵심 지시어는 많아 보여도 실무 우선순위는 분명합니다

    공식 문서에 나오는 지시어는 여러 개지만, 처음 모델을 잡을 때 자주 쓰는 순서는 거의 고정입니다. 보통 FROM → SYSTEM → PARAMETER → MESSAGE가 1차 세트입니다. TEMPLATE와 ADAPTER는 필요성이 분명할 때만 올리는 편이 안전합니다.

    지시어 실무 우선순위 주로 쓰는 상황 주의점
    FROM 최상 출발 베이스 모델 지정 이 선택이 나머지 품질과 호환성의 기준점이 됩니다
    SYSTEM 최상 역할, 금지사항, 출력 순서 고정 길이보다 우선순위가 중요합니다
    PARAMETER 높음 일관성, 컨텍스트, 중단 토큰 제어 값 하나로 안정성이 흔들릴 수 있습니다
    MESSAGE 높음 few-shot 예시 내장 예시를 많이 넣을수록 답변이 경직되기 쉽습니다
    TEMPLATE 중간 채팅 포맷을 명시적으로 제어할 때 베이스 템플릿과 stop이 어긋나면 토큰 누수가 납니다
    ADAPTER 상황 의존 LoRA 또는 QLoRA 결과물 적용 베이스 모델 불일치가 나면 품질이 급격히 무너집니다
    LICENSE 공유 시 중요 배포 또는 팀 공유 내부 사용과 외부 배포 조건을 분리해서 봐야 합니다
    REQUIRES 환경 의존 특정 Ollama 최소 버전 고정 버전 미충족이면 원인 모를 생성 실패처럼 보일 수 있습니다

    효율이 좋은 규칙은 하나입니다. 첫 번째 성공 모델은 단순하게 만들고, 두 번째 버전부터 정교화하는 쪽이 낫습니다. 처음부터 요소를 다 얹으면 실패했을 때 어느 레이어가 원인인지 식별하기가 어렵습니다.

    4. 실전 구현 1단계: 베이스 모델의 원본 템플릿부터 확인하세요

    많이 놓치는 단계인데, 사실상 필수에 가깝습니다. 커스텀 모델을 만들기 전에 ollama show --modelfile로 베이스 모델의 기본 구조를 먼저 봐야 합니다. 이유는 간단합니다. 모델마다 기대하는 채팅 템플릿과 stop 토큰이 다를 수 있기 때문입니다.

    ollama pull llama3.2
    ollama show --modelfile llama3.2
    ollama show --modelfile llama3.2 > base-llama3.2.Modelfile

    저는 보통 세 번째 줄까지 같이 해둡니다. 원본을 파일로 떠놓으면 수정 중에 기준점을 잃지 않거든요. 특히 stop 토큰과 TEMPLATE 블록은 기억으로 다시 쓰기보다 원본에서 필요한 만큼만 복사하는 쪽이 훨씬 안전합니다.

    그다음 1차 버전은 작게 갑니다. 출력 우선순위, 언어 정책, 추측 금지 같은 운영 규약만 넣어도 충분한 경우가 많습니다.

    mkdir -p ~/ollama-custom/workshop
    cd ~/ollama-custom/workshop
    cat > Modelfile <<'EOF'
    FROM llama3.2
    PARAMETER temperature 0.2
    PARAMETER num_ctx 4096
    SYSTEM """당신은 한국어로 답변하는 인프라 엔지니어 보조 AI입니다.
    답변은 항상 다음 순서를 지킵니다.
    1. 바로 실행할 명령어나 설정 예시
    2. 명령어가 실패할 때 확인할 지점
    3. 마지막에 짧은 설명
    모르는 내용은 추측하지 말고 확인이 필요하다고 말합니다."""
    MESSAGE user 리눅스 디스크 사용량 확인 방법 알려줘
    MESSAGE assistant 먼저 아래 명령어부터 확인하겠습니다.
    df -h
    sudo du -sh /var/* 2>/dev/null | sort -h
    lsblk
    EOF
    
    ollama create infra-assistant -f Modelfile
    ollama run infra-assistant

    포인트는 SYSTEM을 길게 쓰는 게 아니라 답변 순서와 금지 규칙을 관찰 가능한 형태로 명시하는 것입니다. “친절하게 답변해라” 같은 추상 문구보다 “명령어 먼저, 설명은 나중” 같은 규칙이 훨씬 잘 먹는 편입니다.

    실제 작업 흐름은 터미널에서 Modelfile을 편집하고 곧바로 create, run으로 검증하는 식으로 진행됩니다.

    5. PARAMETER는 품질보다도 행동 습관을 바꾸는 손잡이입니다

    파라미터를 만질 때 흔히 정답률만 보게 되는데, 실무에선 그보다 출력의 흔들림이 줄어드는가를 먼저 보는 게 맞습니다. 로컬 모델을 업무 보조로 쓸 때는 늘 비슷한 형식으로 나오는 답이 훨씬 값질 때가 많습니다. 이 부분은 실제 운영해 보면 체감이 꽤 큽니다.

    자주 보는 건 temperature, num_ctx, stop, 그리고 필요할 때 샘플링 계열 파라미터입니다. 여기서 특히 실수하기 쉬운 건 num_ctx입니다. 컨텍스트를 크게 잡으면 좋아 보이지만 메모리 사용량과 프롬프트 평가 시간도 같이 늘어납니다.

    파라미터 낮게 둘 때 높게 둘 때 판단 기준
    temperature 일관성 높음, 답변이 보수적 표현 다양성 증가, 흔들림도 증가 운영 문서/명령어 보조면 낮게, 아이디어 발산이면 높게
    num_ctx 메모리 부담 감소, 빠름 긴 문맥 유지에 유리, 느려질 수 있음 평소 넣는 입력 길이를 기준으로 최소 충분치만 확보
    stop 잘 맞으면 출력 경계가 깔끔함 불필요하거나 틀리면 출력이 잘리거나 토큰 누수 베이스 모델 원본 값에서 출발
    MESSAGE 예시 수 유연성 유지 답변 스타일 고정력 상승, 과하면 경직 대개 1~3개로도 충분

    실무적으로는 이렇게 나누면 편합니다.

    • 문서 초안, 운영 절차, 명령어 추천: temperature를 낮게 두고 형식 일관성을 우선합니다.
    • 브레인스토밍, 문안 변주: temperature를 조금 올리되, SYSTEM에서 출력 구조는 유지합니다.
    • 긴 로그/설정 해석: num_ctx를 무작정 키우기보다 먼저 입력을 잘라 넣을 수 있는지 확인합니다.

    6. TEMPLATE는 마지막에 건드리세요. 건드릴 땐 stop까지 세트로 보셔야 합니다

    TEMPLATE는 멋있어 보이지만 실제로는 가장 쉽게 사고 나는 영역입니다. 추천하는 원칙은 단순합니다. 기본 템플릿으로 원하는 형식이 나오면 TEMPLATE는 건드리지 않는 것입니다. SYSTEM과 MESSAGE만으로 해결되는 경우가 훨씬 많습니다.

    반대로 TEMPLATE를 건드려야 하는 경우도 있습니다. 베이스 모델의 채팅 포맷을 명시적으로 유지하면서 시스템, 사용자, 응답 경계를 통제하고 싶을 때입니다. 이때 핵심은 예쁘게 다시 쓰는 게 아니라 원본 구조를 최대한 보존하면서 필요한 부분만 조정하는 겁니다.

    FROM llama3.2
    TEMPLATE """{{ if .System }}<|start_header_id|>system<|end_header_id|>
    
    {{ .System }}<|eot_id|>{{ end }}{{ if .Prompt }}<|start_header_id|>user<|end_header_id|>
    
    {{ .Prompt }}<|eot_id|>{{ end }}<|start_header_id|>assistant<|end_header_id|>
    
    {{ .Response }}<|eot_id|>"""
    PARAMETER stop "<|start_header_id|>"
    PARAMETER stop "<|end_header_id|>"
    PARAMETER stop "<|eot_id|>"
    SYSTEM """당신은 한국어 운영 문서를 절차형으로 정리하는 도우미입니다."""

    이 블록에서 꼭 봐야 할 건 둘입니다.

    • {{ .System }}, {{ .Prompt }}, {{ .Response }}가 실제 템플릿에 반영되는지
    • TEMPLATE에 등장하는 특수 토큰과 PARAMETER stop이 서로 맞물리는지

    실패 사례도 꽤 단순합니다. 템플릿은 바꿨는데 stop은 예전 값을 그대로 두는 경우입니다. 그러면 응답 본문에 <|eot_id|> 같은 문자열이 그대로 보이거나, 반대로 답변이 중간에서 잘립니다. 이건 모델이 멍청해서가 아니라 출력 종료 경계가 틀어진 것입니다.

    7. ADAPTER는 지식 보강보다 정렬된 편향 추가에 가깝게 보는 편이 맞습니다

    ADAPTER를 붙이면 개인화 폭이 넓어지긴 합니다. 다만 기대를 너무 크게 잡으면 실망도 큽니다. 먼저 물어볼 건 하나입니다. “이 문제를 SYSTEM과 MESSAGE로 해결할 수 없는가?” 여기서 해결되면 굳이 어댑터를 안 붙이는 편이 운영은 더 쉽습니다.

    어댑터가 필요한 상황은 보통 두 가지입니다. 첫째, 특정 도메인 문체나 응답 습관을 훨씬 강하게 고정해야 할 때입니다. 둘째, 베이스 모델만으로는 일관되게 안 나오는 패턴을 추가 학습 결과로 밀어 넣고 싶을 때입니다.

    FROM llama3.2
    ADAPTER ./adapters/my-lora-adapter.gguf
    SYSTEM """당신은 Kubernetes 운영 가이드를 작성하는 보조 모델입니다.
    항상 점검 순서, 명령어, 장애 추정 원인을 순서대로 제시합니다."""

    여기서 제일 중요한 건 어댑터가 학습된 베이스 모델과 FROM의 베이스 모델이 맞아야 한다는 점입니다. 공식 문서도 이 경우 결과가 erratic해질 수 있다고 안내합니다. 현장에서는 이걸 모델 성능 탓으로 오해하는 경우가 적지 않습니다.

    그래서 저는 순서를 이렇게 잡습니다.

    1. ADAPTER 없이 동작하는 1차 모델을 만듭니다.
    2. 동일 질문 세트로 결과를 저장합니다.
    3. 그다음 ADAPTER를 붙인 버전을 따로 만듭니다.
    4. 스타일 개선인지, 지식 개선인지, 아니면 품질 붕괴인지 비교합니다.
    Ollama 커스텀 LLM에 LoRA 어댑터를 적용하는 구조 이미지

    베이스 모델 위에 LoRA 어댑터를 추가해 도메인 특화 성격을 강화하는 흐름입니다.

    8. API 자동화는 실험이 많아질수록 가치가 커집니다

    한두 개 만들 땐 Modelfile이 읽기 편합니다. 그런데 역할별 모델이 늘어나면 CLI 수작업은 금방 귀찮아집니다. 이때는 POST /api/create를 써서 생성 과정을 코드로 고정하는 편이 낫습니다. 모델 이름 규칙, 파라미터 조합, 예시 대화 구성을 반복해서 재현해야 할 때 특히 강합니다.

    curl http://localhost:11434/api/create -d '{
      "model": "infra-helper-api",
      "from": "llama3.2",
      "system": "You are a Korean infrastructure engineering assistant. Provide commands first, then failure checks, then short explanations.",
      "parameters": {
        "temperature": 0.2,
        "num_ctx": 4096
      },
      "messages": [
        {
          "role": "user",
          "content": "nginx 로그 확인 순서 알려줘"
        },
        {
          "role": "assistant",
          "content": "먼저 access.log와 error.log를 분리해서 보고, 그다음 4xx/5xx 비율과 upstream 오류를 확인하겠습니다."
        }
      ]
    }'

    이 방식을 선호하는 이유는 단순히 편해서만은 아닙니다. 실험 조건을 텍스트로 남길 수 있기 때문입니다. 어떤 모델 이름이 어떤 규칙과 파라미터로 만들어졌는지가 남아야 나중에 비교가 됩니다.

    생성 후에는 성능 체감만 보지 말고 API 응답 지표도 같이 보는 편이 좋습니다. Ollama의 생성 응답에는 load_duration, prompt_eval_count, prompt_eval_duration, eval_count, eval_duration 같은 필드가 포함됩니다. 이걸 보면 느린 이유가 로딩인지, 입력 길이인지, 출력 토큰 수인지 분리해서 보기 훨씬 편합니다.

    curl http://localhost:11434/api/generate -d '{
      "model": "infra-assistant",
      "prompt": "systemd 서비스 상태 확인 절차를 알려줘",
      "stream": false
    }'

    여기서 판단 포인트는 이렇습니다.

    • load_duration이 크면 모델 로딩 비용이 큰 겁니다.
    • prompt_eval_duration이 크면 입력 문맥이 길거나 num_ctx 운용이 과한 경우를 의심할 수 있습니다.
    • eval_duration이 크면 출력 토큰 수가 많거나 실행 자원이 부족한 쪽일 가능성이 큽니다.

    9. 트러블슈팅은 증상보다 근본 원인을 붙잡아야 빨리 끝납니다

    실제로 많이 부딪히는 문제는 대부분 네 부류였습니다. 중요한 건 증상을 외우는 게 아니라 어느 레이어가 깨졌는지를 먼저 가르는 겁니다. SYSTEM 문제인지, TEMPLATE 문제인지, ADAPTER 문제인지, 자원 문제인지 구분만 잘해도 시간이 꽤 절약됩니다.

    1) 시스템 프롬프트가 먹지 않는 것처럼 보일 때

    • 먼저 볼 것: ollama show --modelfile 모델명
    • 근본 원인: TEMPLATE 안에 {{ .System }} 반영이 없거나 베이스 템플릿을 수정하면서 시스템 경계를 깨뜨린 경우가 많습니다.
    • 판단 기준: 말투보다도 “추측 금지”, “명령어 우선” 같은 규칙이 반복적으로 무시되는지 봅니다.
    • 해결 방향: 원본 TEMPLATE로 되돌린 뒤 SYSTEM만 남기고 다시 비교합니다.

    2) 답변에 특수 토큰이 섞일 때

    • 먼저 볼 것: TEMPLATE와 PARAMETER stop 조합
    • 근본 원인: 출력 종료 토큰과 템플릿 토큰 경계가 맞지 않는 상황입니다.
    • 판단 기준: <|eot_id|>, <|start_header_id|> 같은 문자열이 본문에 보이면 거의 이쪽입니다.
    • 해결 방향: 베이스 모델 기본 Modelfile의 stop 설정을 그대로 기준 삼아 다시 맞춥니다.

    3) 어댑터 적용 후 품질이 갑자기 무너질 때

    • 먼저 볼 것: ADAPTER 학습 기준 모델
    • 근본 원인: FROM에 적은 베이스 모델과 어댑터가 기대하는 기반 모델 불일치
    • 판단 기준: 문장 붕괴, 주제 일탈, 불필요한 반복이 적용 직후 심해집니다.
    • 해결 방향: 어댑터 제작 시 사용한 기반 계열과 동일한 모델로 FROM을 맞춥니다.

    4) 속도가 너무 느리거나 메모리가 버거울 때

    • 먼저 볼 것: ollama ps, OS 메모리/스왑 사용량, 입력 길이, num_ctx
    • 근본 원인: 모델 크기보다도 과도한 컨텍스트, 동시 실행 모델 수, 자원 부족이 겹치는 경우가 많습니다.
    • 판단 기준: 스왑이 발생하면 체감 성능은 급격히 떨어집니다.
    • 해결 방향: 작은 베이스 모델로 내리거나 num_ctx를 낮추고 실험 모델 동시 구동 수를 줄입니다.

    저는 장애를 볼 때 항상 이렇게 나눕니다. 형식 이상은 TEMPLATE, 성격 이상은 SYSTEM/MESSAGE, 지식 이상은 ADAPTER 또는 베이스 모델, 속도 이상은 자원과 컨텍스트. 이 프레임으로 보면 원인 추적 속도가 빨라집니다.

    Ollama 커스텀 LLM 트러블슈팅과 검증 과정을 보여주는 이미지

    응답 토큰 이상 출력, 시스템 프롬프트 무시, 어댑터 불일치 같은 대표 장애 포인트를 점검하는 장면입니다.

    10. 검증은 더 똑똑해졌는가보다 의도대로 수렴하는가를 보셔야 합니다

    커스텀 모델을 만들고 나면 자꾸 성능 향상만 보게 됩니다. 그런데 Modelfile의 1차 목적은 대개 정답률 폭증이 아니라 출력 습관 고정입니다. 그래서 검증 질문 세트를 별도로 두고 베이스 모델과 커스텀 모델을 같은 질문으로 비교하는 편이 좋습니다.

    ollama run llama3.2 "systemd 서비스 상태 확인 절차를 알려줘"
    ollama run infra-assistant "systemd 서비스 상태 확인 절차를 알려줘"
    ollama run infra-assistant "모르면 추측하지 말고, nginx 502 점검 순서를 알려줘"

    여기서 보는 건 단순히 답변 길이나 그럴듯함이 아닙니다.

    1. 명령어가 먼저 나오는지
    2. 설명이 뒤로 밀리는지
    3. 모를 때 지어내지 않는지
    4. 한국어 톤과 형식이 유지되는지
    5. MESSAGE 예시가 답변을 과하게 복제하게 만들지는 않는지
    검증 항목 베이스 모델 커스텀 모델 합격 기준
    답변 언어 영문/국문 혼합 가능 국문 고정 가능 언어 정책이 흔들리지 않을 것
    명령어 우선 제시 질문마다 다름 일관되게 맞출 수 있음 초반 2~3줄 안에 바로 실행 항목이 나올 것
    톤 일관성 변동 가능 SYSTEM으로 안정화 질문이 바뀌어도 문체가 크게 흔들리지 않을 것
    도메인 집중도 일반 지식 중심 MESSAGE/ADAPTER로 강화 쓸데없는 일반론보다 절차형 답변이 앞설 것
    추측 억제 상황 따라 흔들림 금지 규칙 반영 가능 불확실할 때 확인 필요성을 분명히 말할 것

    현업 자동화에서는 “베이스 모델보다 더 똑똑하다”보다 “늘 같은 포맷으로 나온다”가 더 중요할 때가 많습니다. 운영 문서 초안, 반복 질의 응답, 내부 가이드 작성은 특히 그렇습니다. 이 지점이 바로 Ollama 커스텀 LLM의 실무 가치라고 봐도 무리가 없습니다.

    11. FAQ와 최종 추천: Ollama 커스텀 LLM은 이럴 땐 A, 저럴 땐 B로 나누면 편합니다

    자주 헷갈리는 질문

    • Q. Modelfile만 쓰면 미세 조정이 된 건가요?
      A. 아닙니다. 기본적으로는 설정, 프롬프트 구조, 예시 대화, 실행 파라미터를 묶는 작업에 가깝습니다. 추가 학습 결과를 붙이려면 ADAPTER 같은 별도 레이어가 필요합니다.
    • Q. TEMPLATE는 꼭 수정해야 하나요?
      A. 아닙니다. 기본 템플릿으로 문제가 없으면 안 건드리는 쪽이 맞습니다. 깨지는 빈도가 높은 영역이라 이유가 분명할 때만 손대는 게 좋습니다.
    • Q. 개인화된 AI를 가장 빨리 만드는 방법은?
      A. 베이스 모델 하나를 고르고, SYSTEM에 역할과 출력 순서를 명시한 뒤, MESSAGE 1~2개만 넣어 1차 버전을 만드는 방식이 가장 빠릅니다.
    • Q. 언제 ADAPTER까지 가야 하나요?
      A. SYSTEM과 MESSAGE로도 해결되지 않는 도메인 특화 패턴이 반복해서 필요할 때입니다. 단순한 말투 고정 정도라면 대개 과한 선택입니다.

    선택 기준은 이렇게 정리하면 편합니다.

    • 빠르게 목적형 모델이 필요하다: FROM + SYSTEM + PARAMETER + MESSAGE로 시작하세요. 가장 안전하고 재현성이 좋습니다.
    • 응답 형식을 강하게 통제해야 한다: 먼저 ollama show --modelfile로 원본을 확인한 뒤 TEMPLATE를 최소 수정하세요.
    • 특정 학습 결과물을 이미 갖고 있다: ADAPTER를 붙이되, 베이스 모델 호환성을 제일 먼저 확인하세요.
    • 역할별 모델을 반복 생성해야 한다: 사람이 수동으로 만들기보다 /api/create로 자동화하는 편이 관리가 낫습니다.
    • 속도와 메모리가 아슬아슬하다: 큰 모델 집착보다 작은 모델 + 낮은 num_ctx + 명확한 SYSTEM이 더 실용적일 때가 많습니다.

    한 줄로 압축하면 이렇습니다. Ollama 커스텀 LLM의 핵심은 모델을 새로 발명하는 게 아니라, 내 작업 방식을 반복 가능하게 고정하는 것입니다. 처음부터 무거운 학습 파이프라인으로 들어가기보다, Modelfile로 행동 규약을 먼저 굳히는 쪽이 훨씬 현실적입니다. 관련해서 로컬 LLM 성능 최적화나 RAG 비교 글도 함께 읽어보시면 다음 단계 판단이 더 쉬워집니다.

    Ollama 커스텀 LLM Modelfile 핵심 요소 요약 인포그래픽

    FROM, SYSTEM, PARAMETER, TEMPLATE, ADAPTER를 언제 선택하면 되는지 한눈에 정리한 요약 이미지입니다.

  • [AI] Ollama 로컬 LLM 성능 최적화: Apple Silicon Mac에서 Flash Attention 및 NPU 활용법

    [AI] Ollama 로컬 LLM 성능 최적화: Apple Silicon Mac에서 Flash Attention 및 NPU 활용법

    Ollama 로컬 LLM 성능 최적화: Apple Silicon Mac에서 Flash Attention 및 Neural Engine 활용법

    안녕하세요! 13년차 서버실 지킴이, 인프라 엔지니어입니다. 요즘 로컬 LLM(Large Language Model) 돌리는 재미에 푹 빠져있어요. 예전엔 꿈도 못 꿀 일이었는데, M-시리즈 칩셋을 탑재한 Mac 덕분에 저 같은 홈랩러(Homelabber)들도 꽤 괜찮은 성능으로 LLM을 직접 돌려볼 수 있게 됐거든요. 특히 Ollama는 복잡한 설정 없이도 다양한 모델을 쉽게 구동할 수 있어서 정말 편하더라고요.

    그런데 말이에요, 단순히 모델만 다운받아 실행한다고 끝이 아니더라고요. 처음엔 생각보다 느린 응답 속도에 살짝 실망하기도 했어요. 😅 ‘이게 최선인가?’ 싶어서 이것저것 만져보다가, 결국 몇 가지 설정을 통해 체감 성능을 확 끌어올리는 데 성공했습니다. 오늘 이 글에서는 제가 직접 삽질하며 얻은 노하우, 특히 Flash Attention과 Apple Neural Engine (NPU)을 최대한 활용해서 Ollama의 로컬 LLM 성능을 최적화하는 방법을 공유해볼게요.

    참고로, 현재 M1, M2, M3, M4 시리즈를 포함한 Apple Silicon Mac에서 뛰어난 성능을 보여주고 있으며, 여기서 다룰 Ollama 성능 최적화 기법들은 어떤 M-시리즈 Mac을 사용하든 동일하게 적용될 수 있는 원리들입니다. 지금 사용 중인 M-시리즈 Mac에서도 충분히 효과를 보실 수 있을 거예요!

    Ollama와 Apple Silicon Mac을 활용한 로컬 LLM 아키텍처 다이어그램

    로컬 LLM 워크플로우를 보여주는 Apple Silicon Mac 기반의 아키텍처 다이어그램입니다. Ollama가 Llama.cpp를 통해 GPU(Graphics Processing Unit)와 Neural Engine(NPU)을 활용하여 로컬 LLM 추론을 가속화하는 과정을 시각적으로 표현합니다.

    개념 설명: Flash Attention과 Apple Neural Engine, 왜 중요할까요?

    Ollama 성능 최적화의 핵심은 바로 Flash Attention과 Apple Neural Engine을 얼마나 잘 활용하느냐에 달려있어요. 쉽게 설명해볼게요.

    1. Flash Attention (플래시 어텐션)

      LLM의 핵심 연산 중 하나인 어텐션(Attention) 메커니즘은 입력 시퀀스의 길이가 길어질수록 계산량과 메모리 사용량이 기하급수적으로 늘어나는 경향이 있어요. Flash Attention은 이 어텐션 계산을 훨씬 효율적으로 수행하도록 고안된 기술이거든요. 특히 GPU의 고대역폭 메모리(HBM) 활용을 최적화해서, 메모리 접근 횟수를 줄이고 계산 속도를 획기적으로 높여줍니다. 쉽게 말해, ‘GPU 메모리를 덜 쓰고 더 빠르게 어텐션 연산을 처리하는 마법 같은 기술’이라고 생각하면 돼요. Ollama는 내부적으로 llama.cpp를 사용하는데, llama.cpp는 이러한 효율적인 어텐션 메커니즘을 포함한 다양한 GPU 최적화 기법들을 활용하고 있거든요. 덕분에 우리는 직접 Flash Attention을 코딩하지 않아도 그 혜택을 볼 수 있는 거죠!

    2. Apple Neural Engine (NPU, 뉴럴 프로세싱 유닛)

      NPU는 신경망(Neural Network) 연산에 특화된 하드웨어 가속기예요. Apple Silicon 칩셋에 통합되어 있는 Neural Engine이 바로 NPU의 한 종류인데요. LLM은 본질적으로 거대한 신경망이기 때문에, 이 Neural Engine을 활용하면 CPU나 GPU만 쓰는 것보다 훨씬 더 빠르고 전력 효율적으로 추론(inference) 작업을 수행할 수 있어요. Ollama는 llama.cpp를 통해 이 Neural Engine을 적극적으로 활용하도록 설계되어 있거든요. ‘AI 연산 전용 고속도로’를 깔아주는 것과 같다고 보면 돼요. 이 NPU를 최대한 활용하는 것이 Mac에서 로컬 LLM 성능을 끌어올리는 중요한 포인트입니다.

    실전 구현: Ollama 설정으로 성능 한계 돌파하기

    이제 본격적으로 Ollama의 성능을 최적화해볼 시간이에요. 몇 가지 단계만 거치면 됩니다!

    1. Ollama 설치 및 기본 모델 다운로드

    아직 Ollama가 설치되어 있지 않다면, 공식 웹사이트에서 다운로드하여 설치해주세요. 터미널에서 다음 명령어로 llama2 모델을 받아봐요.

    
    ollama pull llama2
    

    처음엔 이렇게 기본 모델로 테스트하는 게 좋더라고요. 모델 다운로드가 완료되면, 간단히 실행해서 기본 성능을 확인해보세요.

    
    ollama run llama2
    >>> Why is the sky blue?
    

    2. Modelfile을 이용한 GPU/Neural Engine 최적화

    Ollama는 Modelfile이라는 것을 통해 모델의 동작 방식을 세밀하게 제어할 수 있어요. 이 Modelfile을 수정해서 GPU와 Neural Engine을 최대한 활용하도록 설정하는 게 핵심입니다.

    먼저, 기존 모델의 Modelfile을 복사해서 새로운 Modelfile을 만들어봅시다. 저는 llama2-optimized라는 이름으로 만들어볼게요.

    
    ollama show llama2 --modelfile > Modelfile.llama2-optimized
    

    이제 Modelfile.llama2-optimized 파일을 열어서 다음 내용을 추가하거나 수정해주세요. 특히 PARAMETER 부분에 주목해주세요.

    
    FROM llama2
    
    # GPU (Neural Engine 포함)를 최대한 활용하도록 설정합니다.
    # Apple Silicon의 경우, '1'로 설정하면 GPU 및 Neural Engine을 사용합니다.
    PARAMETER num_gpu 1
    
    # 컨텍스트 길이 (Context Length)를 조절합니다.
    # 모델이 한 번에 처리할 수 있는 토큰의 최대 길이입니다.
    # 메모리 제약이 있다면 이 값을 줄여야 할 수도 있어요. 기본값은 2048.
    PARAMETER num_ctx 4096
    
    # 스레드 수를 설정합니다. CPU 코어 수에 맞춰 조절할 수 있어요.
    # 보통 시스템의 논리 코어 수 정도로 설정하는 게 좋습니다.
    PARAMETER num_thread 8
    
    # 시스템 프롬프트: 모델의 행동을 미리 정의합니다.
    SYSTEM "You are a helpful AI assistant. Respond concisely."
    
    # 추가적인 템플릿 설정 (모델에 따라 다를 수 있음)
    TEMPLATE """[INST] {{ .Prompt }} [/INST]"""
    

    여기서 중요한 파라미터들은 다음과 같아요.

    • PARAMETER num_gpu 1: 이 설정이 바로 Ollama에게 ‘GPU를 사용해!’라고 알려주는 부분이에요. Apple Silicon Mac에서는 이 값을 1로 설정하면 내장된 GPU와 Neural Engine을 활용하게 돼요. 이 값을 빼먹으면 CPU로만 돌게 되어 성능이 크게 저하될 수 있으니 꼭 넣어주세요!
    • PARAMETER num_ctx 4096: 컨텍스트 길이예요. LLM이 이전 대화를 얼마나 기억할지 결정하는 부분이거든요. 길게 가져갈수록 더 많은 정보를 기억하지만, 그만큼 메모리 사용량도 늘어나요. Mac의 램(RAM) 용량에 맞춰 적절히 조절해야 해요. 8GB 램이라면 2048 정도, 16GB 이상이라면 4096이나 그 이상으로 시도해볼 만합니다.
    • PARAMETER num_thread 8: 모델 추론에 사용할 CPU 스레드 수예요. 보통 Mac의 논리 코어 수에 맞춰 설정하는 게 좋아요. ‘활성 상태 보기’에서 CPU 코어 수를 확인해보세요.
    Ollama Modelfile 편집 화면 및 최적화 파라미터 설정

    Modelfile의 핵심 파라미터인 num_gpu, num_ctx, num_thread 설정을 보여주는 화면이에요. 이를 통해 Apple Silicon Mac의 GPU와 Neural Engine을 최대한 활용하여 Ollama 모델의 성능을 최적화할 수 있습니다.

    3. 최적화된 모델 생성 및 실행

    Modelfile을 저장했다면, 이제 이 파일을 기반으로 새로운 Ollama 모델을 생성해요.

    
    ollama create llama2-optimized -f Modelfile.llama2-optimized
    

    생성된 모델을 실행하고 성능을 체감해봅시다!

    
    ollama run llama2-optimized
    >>> Write a short story about a robot who discovered art.
    

    이전보다 훨씬 빠른 응답 속도를 느끼실 거예요. 🎉

    4. 양자화(Quantization)를 통한 추가 최적화

    모델의 크기를 줄여서 메모리 사용량을 줄이고 속도를 높이는 방법 중 하나가 양자화(Quantization)예요. 모델의 가중치(weights)를 더 낮은 정밀도(예: 32비트 부동소수점에서 4비트 정수로)로 표현하는 기술이거든요. Ollama는 다양한 양자화된 모델을 제공하고 있어요.

    예를 들어, llama2:7b-chat-q4_0처럼 q4_0은 4비트 양자화된 모델을 의미합니다. 숫자가 낮을수록 모델 크기가 작고 빠르지만, 정확도는 약간 떨어질 수 있어요. 자신의 Mac 성능과 필요한 정확도를 고려해서 적절한 양자화 레벨의 모델을 선택하는 게 좋습니다. 저는 주로 q4_0이나 q5_1을 즐겨 써요.

    
    ollama pull llama2:7b-chat-q4_0
    ollama run llama2:7b-chat-q4_0
    

    ⚠️ 주의사항 및 트러블슈팅: 삽질은 저만 하세요!

    제가 겪었던 몇 가지 문제와 해결법을 공유해요.

    • GPU 사용률이 생각보다 낮아요!

      가장 먼저 Modelfile의 PARAMETER num_gpu 1 설정이 제대로 되어 있는지 확인하세요. 그리고 Mac의 ‘활성 상태 보기(Activity Monitor)’에서 ‘GPU 기록’ 탭을 보면 GPU 사용량을 확인할 수 있어요. 만약 여전히 낮다면, Ollama가 llama.cpp를 통해 GPU 자원을 제대로 인식하지 못하는 경우일 수도 있어요. Ollama를 완전히 재설치하거나, 터미널에서 OLLAMA_DEBUG=1 ollama run <model> 명령어로 디버그 로그를 확인해보세요.

    • 메모리 부족 오류 (Out of Memory Error)!

      이건 주로 num_ctx 값이 너무 높거나, 모델 자체가 너무 커서 Mac의 RAM이 부족할 때 발생해요. 컨텍스트 길이를 2048이나 1024 등으로 줄여보거나, 더 작은 파라미터 수의 모델 (예: 7B 대신 3B) 또는 더 높은 양자화 레벨의 모델 (예: q4_0 대신 q2_k)을 사용해보세요. 팁: 환경 변수 OLLAMA_MAX_RAM=8GB처럼 명시적으로 Ollama가 사용할 최대 램을 제한할 수도 있어요. 저는 16GB Mac에서 OLLAMA_MAX_RAM=12GB 정도로 설정해봤습니다.

    • 처음엔 빨랐는데 점점 느려져요!

      오랜 시간 사용하거나, 다른 무거운 애플리케이션이 동시에 실행 중일 때 발생할 수 있어요. Mac의 시스템 리소스를 점유하는 다른 프로세스가 있는지 ‘활성 상태 보기’를 통해 확인해보세요. 재부팅하거나 Ollama를 다시 시작하는 것만으로도 해결될 때가 많아요. 캐시 문제일 수도 있으니 ollama run --reset <model> 명령어를 시도해보는 것도 방법입니다.

    검증 및 결과: 눈으로 확인하는 성능 향상

    최적화가 잘 되었는지 확인하는 가장 좋은 방법은 ‘활성 상태 보기’와 실제 추론 속도를 비교해보는 거예요.

    1. ‘활성 상태 보기’로 GPU/Neural Engine 사용량 확인

      Ollama 모델을 실행하면서 ‘활성 상태 보기’의 ‘GPU 기록’ 탭과 ‘CPU’ 탭을 확인해보세요. num_gpu 1 설정을 적용한 후에는 GPU 사용량이 확연히 증가하고, CPU 사용량은 상대적으로 안정화되는 것을 볼 수 있을 거예요. 특히 M-시리즈 칩셋의 Neural Engine도 백그라운드에서 활발하게 동작하는 것을 체감할 수 있습니다.

    2. 간단한 벤치마크 스크립트

      파이썬 스크립트를 사용해서 간단하게 응답 속도를 측정해볼 수 있어요.

      
      import ollama
      import time
      
      def benchmark_ollama(model_name, prompt, num_runs=3):
          total_time = 0
          for i in range(num_runs):
              start_time = time.time()
              response = ollama.chat(model=model_name, messages=[{'role': 'user', 'content': prompt}])
              end_time = time.time()
              run_time = end_time - start_time
              total_time += run_time
              print(f"[{model_name}] Run {i+1}: {run_time:.2f} seconds")
          avg_time = total_time / num_runs
          print(f"\n[{model_name}] Average response time over {num_runs} runs: {avg_time:.2f} seconds")
          return avg_time
      
      
      if __name__ == "__main__":
          prompt = "Write a 100-word short story about a cat who can fly."
          
          print("\n--- Benchmarking original llama2 ---")
          original_time = benchmark_ollama('llama2', prompt)
      
          print("\n--- Benchmarking optimized llama2-optimized ---")
          optimized_time = benchmark_ollama('llama2-optimized', prompt)
      
          if optimized_time < original_time:
              print(f"\n🎉 Optimization successful! Optimized model is {original_time / optimized_time:.2f} times faster!")
          else:
              print("\n😔 Optimization did not yield expected results. Check your settings.")
      

      위 스크립트를 실행해보면 최적화된 모델의 응답 속도가 훨씬 빠르다는 것을 숫자로 확인할 수 있을 거예요. 저도 이 스크립트로 llama2와 llama2-optimized 모델을 비교해보니, 2배 이상의 속도 향상을 경험했어요. 드디어 됐다! 싶었죠. 😄

    Ollama 실행 중 Mac 활성 상태 보기의 GPU 및 Neural Engine 사용량

    Ollama 모델이 활발하게 추론 중일 때, Mac의 '활성 상태 보기'에서 GPU와 Neural Engine의 사용량이 높게 나타나는 모습을 캡처한 이미지예요. 이를 통해 최적화 설정이 제대로 작동하고 있음을 시각적으로 확인할 수 있습니다.

    마무리: 더 빠른 로컬 LLM, 이제 직접 경험해보세요!

    오늘은 Apple Silicon Mac에서 Ollama 로컬 LLM의 성능을 최적화하는 방법에 대해 자세히 알아봤어요. Flash Attention과 같은 효율적인 어텐션 메커니즘을 llama.cpp가 활용하고, Apple Neural Engine을 적극적으로 사용하도록 Modelfile을 설정하는 것이 핵심이었죠. 제가 직접 해보니, 이 작은 설정 변경만으로도 체감 성능이 정말 드라마틱하게 달라지더라고요. 처음엔 이게 뭔가 싶었는데, 막상 적용하고 나니 정말 편하고 좋았어요.

    로컬 LLM은 외부 API 사용료 걱정 없이, 내 데이터 프라이버시 걱정 없이 자유롭게 실험해볼 수 있다는 큰 장점이 있어요. 오늘 알려드린 최적화 팁들을 활용해서 여러분의 Mac에서도 쾌적한 로컬 LLM 환경을 구축해보시길 바랍니다. 혹시 이런 경험 있으신가요? 댓글로 여러분의 삽질 경험이나 팁도 공유해주세요!

    다음 글에서는 Ollama에서 특정 모델을 파인튜닝(Fine-tuning)하는 방법에 대해 다뤄볼까 합니다. 기대해주세요!

    Ollama 로컬 LLM 최적화 전후 성능 비교 인포그래픽

    Ollama 로컬 LLM의 최적화 전후 성능을 비교하는 인포그래픽이에요. Modelfile 설정 변경과 NPU 활용을 통해 응답 속도가 얼마나 향상되었는지 시각적으로 보여줍니다.