테마 전환

Ollama GPU 스케줄링과 리소스 관리: VRAM 최적화와 멀티 GPU 부하 분산

Easton editorial illustration: agentic retrieval reasoning table

8GB VRAM 그래픽 카드에 13B 모델을 간신히 로드했는데, 몇 번 추론하자 갑자기 프로세스가 종료되면서 화면에 CUDA out of memory 오류가 나타납니다.

또는 비용을 들여 GPU 두 장을 장착하고 이제 대형 모델을 돌릴 수 있겠다고 기대했지만, nvidia-smi를 열어 보니 한 장만 작업하고 다른 한 장은 놀고 있을 수도 있습니다.

Ollama를 처음 쓸 때 저도 VRAM 부족, 활용되지 않는 멀티 GPU, 들쭉날쭉한 추론 속도 같은 문제를 겪었습니다. 시행착오 끝에 Ollama의 GPU 스케줄링 논리를 이해하고 나니, 많은 설정은 단순히 구성만 마친다고 바로 활용되는 것이 아니라 매개변수의 원리까지 알아야 한다는 점을 알게 됐습니다.

이 글에서는 다음과 같은 실제 문제를 해결할 수 있도록 그 경험을 정리합니다.

  • 8GB VRAM에서 OOM 없이 13B 모델을 안정적으로 실행하는 방법
  • 멀티 GPU를 실제로 활용하는 구성 방법과 전체 부하 분산 방안
  • VRAM이 부족할 때 조정할 매개변수와 우선순위
  • GPU offloading의 의미와 llama.cpp의 내부 동작 방식

먼저 전제가 하나 있습니다. 이 글은 GPU, CUDA, Ollama 기본 사용법을 어느 정도 알고 있어야 이해하기 쉬운 심화 내용입니다. Ollama를 처음 접한다면 시리즈 앞부분, 특히 여섯 번째 성능 최적화 기초 글을 먼저 읽고 배경지식을 쌓은 뒤 보는 편이 좋습니다.

1. GPU 메모리 관리 방식: 매개변수 구성 완전 해설

Ollama의 GPU 스케줄링은 몇 가지 매개변수로 모델 계층을 GPU와 CPU에 어떻게 나눌지 제어하는 것이 핵심입니다. 이를 이해해야 VRAM이 충분해 보이는데도 오류가 발생하거나 추론 속도가 갑자기 느려지는 이유를 파악할 수 있습니다.

1.1 핵심 매개변수 자세히 보기

먼저 가장 중요한 매개변수를 한눈에 비교할 수 있도록 표로 정리했습니다.

매개변수역할기본값조정이 필요한 경우
num_gpuGPU에서 실행할 모델 계층 수자동 감지VRAM이 부족할 때 값을 줄임
main_gpu주 GPU 번호0멀티 GPU에서 사용할 카드를 지정할 때
low_vram저용량 VRAM 모드 스위치falseVRAM이 8GB 미만이면 활성화 권장
num_batch배치 크기512VRAM이 빠듯하면 256으로 낮춤
num_ctx컨텍스트 길이4096짧은 대화에서는 2048로 줄여 VRAM 절약

이 가운데 num_gpu는 특히 혼동하기 쉽습니다. 보유한 GPU 개수가 아니라 모델 계층 중 GPU에서 연산할 계층 수를 뜻합니다.

예를 들어 Llama 2 7B 모델에는 32개 계층이 있습니다. num_gpu: 32로 설정하면 32개 계층 전체를 GPU에서 실행합니다. VRAM이 부족해 num_gpu: 20으로 바꾸면 20개 계층은 GPU에서, 나머지 12개 계층은 CPU에서 연산하므로 당연히 속도가 느려집니다.

low_vram도 알아 둘 만한 매개변수입니다. 활성화하면 Ollama는 KV cache를 GPU VRAM이 아닌 CPU 메모리에 두는 등의 방법으로 VRAM을 절약합니다. 추론 속도가 다소 떨어지지만 프로세스가 중단되는 상황은 피할 수 있습니다.

1.2 VRAM 할당 과정

Ollama가 모델을 로드할 때 VRAM은 다음 순서로 할당됩니다.

  1. VRAM 감지: GPU의 여유 VRAM 확인
  2. 계층 수 계산: 모델 크기와 VRAM 상태에 따라 GPU에 올릴 수 있는 계층 수 계산
  3. KV cache 할당: 추론 캐시를 위한 공간 예약. 이 공간도 VRAM을 사용함
  4. 추론 시작: 추론 중 VRAM 사용량이 동적으로 변동

핵심은 두 번째 단계입니다. Ollama가 최적의 계층 배치를 자동으로 계산하지만, VRAM이 충분한 듯하면서도 빠듯한 경계 상황에서는 계산이 정확하지 않을 수 있습니다. 8GB VRAM으로 13B 모델을 실행하는 경우가 대표적이며, 이때는 num_gpu를 직접 지정해야 합니다.

현재 모델에서 GPU offloading된 계층 수를 확인하려면 다음 명령을 사용합니다.

ollama run llama3 --verbose

출력에 llama_model_load: model loaded - layers: 40/40 on GPU와 비슷한 줄이 표시됩니다. 이는 40개 계층 모두 GPU에 올라갔다는 뜻입니다.

1.3 llama.cpp 백엔드 동작 방식

Ollama는 내부적으로 llama.cpp 추론 엔진을 사용합니다. llama.cpp의 GPU offloading 방식을 이해하면 매개변수를 바꿔도 효과가 크지 않은 경우가 생기는 이유를 알 수 있습니다.

GPU Offloading 결정 방식

llama.cpp는 대략 다음과 같이 계산합니다.

사용 가능한 VRAM = GPU 총 VRAM - 시스템 예약 공간(대략 수백 MB)
계층당 크기 = 모델 매개변수 / 계층 수
GPU에 올릴 수 있는 계층 수 = min(전체 계층 수, 사용 가능한 VRAM / 계층당 크기)

이 계산에는 함정이 있습니다. 모델 자체가 차지하는 VRAM만 고려하고 KV cache는 포함하지 않습니다. KV cache는 추론에 사용하는 캐시이며 대화가 길어질수록 커집니다. 이 때문에 모델 로드는 성공했지만 몇 번 추론한 뒤 KV cache가 VRAM을 소진해 프로세스가 중단될 수 있습니다.

75%
Q4 양자화로 절약되는 VRAM
Source: 실측 비교: FP16 → Q4_K_M

혼합 연산 구조

GPU와 CPU의 역할이 완전히 분리되는 것은 아닙니다. 대략 다음과 같이 나뉩니다.

  • GPU 담당: 행렬 연산과 attention 연산처럼 계산량이 큰 작업
  • CPU 담당: embedding과 normalization처럼 계산량이 작은 작업
  • 데이터 전송: GPU와 CPU 사이에서 데이터를 주고받는 데 비용 발생

일부 계층만 GPU에 올리면 데이터 전송 비용이 커집니다. 한 계층의 연산이 끝난 뒤 다음 계층이 다른 장치에 있으면 결과를 먼저 전송해야 하기 때문입니다. 일부 GPU offloading에서 추론 속도가 눈에 띄게 느려지는 이유가 바로 이것입니다.

mmap 메모리 매핑

llama.cpp는 기본적으로 mmap을 사용해 모델 파일을 로드합니다. 장점은 다음과 같습니다.

  • 전체 모델을 한꺼번에 메모리로 읽지 않고 운영체제가 필요할 때만 로드함
  • 여러 프로세스가 같은 메모리 데이터를 공유할 수 있음
  • 메모리 사용량이 적음

특정 상황에서 문제가 생겨 mmap을 비활성화하려면 Modelfile에 다음과 같이 설정합니다.

PARAMETER use_mmap false

2. 멀티 GPU 구성 방법: 전체 부하 분산 구조

GPU를 두 장 이상 가진 사용자가 가장 답답해하는 문제는 Ollama에서 이 GPU를 모두 활용하는 방법입니다.

먼저 실망스러운 사실부터 짚겠습니다. Ollama는 모델 병렬화를 지원하지 않습니다. 즉, 하나의 모델을 둘로 나눠 절반은 GPU 0에서, 나머지 절반은 GPU 1에서 연산할 수 없습니다. 각 모델은 GPU 한 장에만 연결할 수 있습니다.

그렇다면 멀티 GPU는 어디에 쓸 수 있을까요? 두 가지 용도가 있습니다.

  1. 서로 다른 모델 인스턴스 실행: GPU 0에서 llama3, GPU 1에서 mistral 실행
  2. 같은 모델의 여러 인스턴스 실행: 부하를 분산해 처리량 향상

2.1 단일 인스턴스 멀티 GPU: 한계와 구성

Ollama가 여러 GPU를 인식하게 하는 가장 간단한 방법은 CUDA_VISIBLE_DEVICES 환경 변수를 사용하는 것입니다.

# Ollama에서 GPU 0과 GPU 1만 사용
CUDA_VISIBLE_DEVICES=0,1 ollama serve

하지만 이렇게 구성해도 문제가 있습니다. Ollama는 기본적으로 모델을 GPU 0에만 올리므로 GPU 1은 여전히 유휴 상태입니다. main_gpu 매개변수로 주 GPU를 지정할 수는 있습니다.

# Modelfile
FROM llama3
PARAMETER main_gpu 1  # 주 GPU를 GPU 1로 지정

그러나 이 방식의 활용도는 높지 않습니다. 모델을 실행할 카드를 바꾼 것뿐이며 두 GPU의 성능을 함께 사용하는 것은 아닙니다.

2.2 여러 인스턴스로 부하 분산하기: 권장 방법

멀티 GPU의 성능을 제대로 활용하려면 여러 Ollama 인스턴스를 실행해 각 인스턴스를 GPU 한 장에 연결하고, 부하 분산 장치로 요청을 배분해야 합니다.

구조는 다음과 같습니다.

┌─────────┐
│ Client  │  추론 요청 전송
└────┬────┘

┌────▼────────────────────┐
│ Nginx (부하 분산 장치)    │  least_conn 전략
│ Port: 8080              │
└────┬─────────┬──────────┘
     │         │
┌────▼───┐ ┌──▼────┐
│Ollama 1│ │Ollama 2│
│GPU 0   │ │GPU 1   │  각 인스턴스가 GPU 한 장 독점
│Port    │ │Port    │
│11434   │ │11435   │
└────────┘ └────────┘

1단계: 여러 Ollama 인스턴스 시작

# 인스턴스 1 - GPU 0에 연결, 포트 11434
CUDA_VISIBLE_DEVICES=0 OLLAMA_HOST=127.0.0.1:11434 ollama serve &

# 인스턴스 2 - GPU 1에 연결, 포트 11435
CUDA_VISIBLE_DEVICES=1 OLLAMA_HOST=127.0.0.1:11435 ollama serve &

참고로 Ollama의 기본 데이터 디렉터리는 ~/.ollama이며, 두 인스턴스가 같은 모델 저장소를 공유합니다. mmap 메모리 매핑을 사용하면 여러 프로세스가 동일한 모델 파일을 공유할 수 있으므로 문제되지 않습니다.

2단계: Nginx 부하 분산 구성

# /etc/nginx/conf.d/ollama.conf
upstream ollama_cluster {
    least_conn;  # 연결 수가 가장 적은 인스턴스 우선
    server 127.0.0.1:11434;
    server 127.0.0.1:11435;
}

server {
    listen 8080;

    location / {
        proxy_pass http://ollama_cluster;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;

        # 스트리밍 응답 지원
        proxy_buffering off;
        proxy_cache off;
    }
}

least_conn은 새 요청을 현재 연결 수가 가장 적은 인스턴스로 보내는 전략입니다. 이를 사용하면 두 GPU의 부하가 더욱 고르게 분산됩니다.

3단계: 클라이언트 호출

클라이언트는 Nginx 포트에만 연결하면 됩니다.

# Nginx를 통해 호출하면 인스턴스 하나로 자동 분산됨
curl http://localhost:8080/api/generate -d '{
  "model": "llama3",
  "prompt": "안녕하세요"
}'

또는 Ollama 클라이언트의 기본 주소를 바꿉니다.

export OLLAMA_HOST=http://localhost:8080
ollama run llama3

2.3 부하 분산 전략 비교

Nginx는 사용 목적이 서로 다른 여러 부하 분산 전략을 지원합니다.

전략원리적합한 상황
라운드 로빈(기본값)각 인스턴스에 순서대로 전송단순한 환경, 모델 크기가 같은 경우
최소 연결(least_conn)현재 가장 여유로운 인스턴스로 전송추론 서비스에 권장
IP Hash같은 IP를 항상 같은 인스턴스로 전송세션 유지가 필요한 경우

추론 요청의 처리 시간은 일정하지 않습니다. 몇 초 만에 끝나는 요청도 있고 몇 분이 걸리는 요청도 있습니다. 라운드 로빈을 쓰면 한 인스턴스는 과부하 상태인데 다른 인스턴스는 놀고 있을 수 있습니다. least_conn은 이런 상황을 피할 수 있습니다.

요청을 고르게 보내면서 특정 인스턴스에 장애가 발생했을 때 자동으로 전환하려면 상태 확인 설정을 추가할 수 있습니다.

upstream ollama_cluster {
    least_conn;
    server 127.0.0.1:11434 max_fails=3 fail_timeout=30s;
    server 127.0.0.1:11435 max_fails=3 fail_timeout=30s;
}

특정 인스턴스에서 오류가 세 번 연속 발생하면 Nginx가 해당 인스턴스를 잠시 클러스터에서 제외하고 30초 뒤에 다시 연결을 시도합니다.

3. VRAM 최적화 전략: 양자화, 컨텍스트, 배치 조합 활용

VRAM이 부족할 때는 양자화 > 컨텍스트 길이 > 배치 크기 > GPU 계층 수 순으로 매개변수를 조정해야 합니다.

이 순서가 중요한 이유는 양자화의 영향이 가장 크기 때문입니다. 같은 모델이라도 Q4 양자화는 FP16보다 VRAM을 75% 절약할 수 있습니다. GPU 계층 수를 줄이는 방법은 연산을 GPU에서 CPU로 옮길 뿐이라 VRAM은 절약되지만 속도가 떨어집니다.

3.1 양자화 수준 선택

양자화는 모델 매개변수를 더 적은 비트 수로 저장하는 방법입니다. FP16은 매개변수 하나에 16비트를 사용하지만 Q4는 4비트만 사용합니다. 비트 수가 줄면 정확도도 어느 정도 낮아지지만, 실제 테스트에서 Q4 양자화의 성능 손실은 약 2~3%에 그쳐 대부분의 환경에서 충분히 감수할 수 있습니다.

양자화 수준을 비교하면 다음과 같습니다.

양자화VRAM 사용량(FP16 대비)정확도 손실적합한 상황
Q4_K_M약 25%2~3%성능과 품질의 균형이 필요할 때 권장
Q5_K_M약 33%1~2%정확도가 조금 더 중요한 경우
Q8_0약 50%0.5%원본에 가까운 정확도가 필요한 경우
FP16100%없음연구와 벤치마크 테스트

실제 데이터 참고: Llama 2 13B 모델

  • FP16: 약 26GB VRAM
  • Q4_K_M: 약 8GB VRAM
  • Q8_0: 약 13GB VRAM

따라서 8GB VRAM에는 13B Q4 모델이 간신히 들어갑니다. 하지만 KV cache 공간도 필요하므로 추론할 때 VRAM이 소진되기 쉽습니다.

양자화 수준을 선택할 때 일상적인 용도라면 Q4_K_M이면 충분합니다. 번역이나 코드 생성처럼 정확도가 중요한 작업에는 Q5_K_M 또는 Q8_0을 고려할 수 있습니다.

Ollama는 기본적으로 Q4 양자화 버전을 다운로드합니다. 다른 양자화 버전을 사용하려면 모델 이름 뒤에 접미사를 붙입니다.

# Q4 양자화(기본값)
ollama pull llama3

# Q8 양자화
ollama pull llama3:8b-q8_0

3.2 컨텍스트 길이 최적화

KV cache는 이전 대화 기록을 저장하는 추론 캐시입니다. VRAM 사용량은 컨텍스트 길이와 직접 연결됩니다.

추정 공식(단순화):

KV Cache VRAM ≈ num_ctx × num_layers × hidden_dim × 2 bytes

Llama 2 7B를 예로 들면 다음과 같습니다.

  • num_layers = 32
  • hidden_dim = 4096
  • num_ctx = 4096

이 값으로 계산하면 KV cache는 약 2GB입니다. ctx를 8192로 늘리면 KV cache도 4GB가 됩니다. 컨텍스트가 두 배가 되면 KV cache VRAM도 두 배가 됩니다.

최적화 전략:

  1. 짧은 대화: num_ctx: 2048 사용

    • KV cache VRAM을 절반으로 줄일 수 있음
    • 일상적인 질의응답과 간단한 작업에는 충분함
  2. 긴 문서 처리: ctx를 16000 이상으로 바로 설정하지 말고 chunking 전략 사용

    • 문서를 작은 조각으로 나눠 차례로 처리
    • 한 번에 넣는 것보다 안정적이고 제어하기 쉬움

Modelfile에서는 컨텍스트 길이를 다음과 같이 설정합니다.

FROM llama3
PARAMETER num_ctx 2048  # 컨텍스트 길이 줄이기

여기서 자주 하는 오해가 있습니다. ctx를 작게 설정하면 출력 품질이 낮아진다고 생각하기 쉽지만 그렇지 않습니다. ctx는 모델이 이전 대화 중 얼마나 많은 내용을 기억할 수 있는지에만 영향을 줍니다. 대화가 원래 몇 차례에 그친다면 ctx를 2048로 설정해도 4096으로 설정했을 때와 결과가 같습니다.

3.3 배치와 동시 요청 최적화

num_batch는 한 번에 처리할 token 수를 제어합니다. 기본값이 512이면 Ollama가 한 번에 512개의 token을 처리한다는 뜻입니다.

배치가 크면 병렬 연산 효율이 높아지지만, 그만큼 최대 VRAM 사용량도 증가합니다.

VRAM이 빠듯할 때 배치를 줄이면 순간적인 사용량을 낮출 수 있습니다.

FROM llama3
PARAMETER num_batch 256  # 512에서 256으로 줄임

실제 테스트에서 batch를 512에서 256으로 낮추면 최대 VRAM 사용량이 약 20% 줄었습니다. 추론 속도는 다소 느려지지만 GPU 계층 수를 줄일 때만큼 크게 떨어지지는 않습니다.

동시 요청 문제

Ollama는 기본적으로 요청을 직렬로 처리합니다. 하나의 요청을 마친 다음 다음 요청을 처리합니다. 여러 요청을 동시에 보내면 대기열에서 기다리게 됩니다.

동시 처리 능력을 높이는 방법은 두 가지입니다.

  1. 여러 인스턴스 배포: 앞에서 설명한 멀티 GPU 부하 분산 방식으로 각 인스턴스가 독립적으로 요청 처리
  2. 대기열 시스템: 애플리케이션 계층에 Redis Queue 같은 대기열을 두고 요청 분배를 직접 관리

두 번째 방법은 멀티 GPU가 없는 환경에 더 적합합니다. 애플리케이션 코드에서는 다음과 같이 처리할 수 있습니다.

import redis
from queue import Queue

# Redis를 대기열로 사용
r = redis.Redis()
r.lpush('ollama_queue', request_data)

# 백그라운드 worker가 대기열에서 요청을 가져와 처리
request = r.rpop('ollama_queue')
ollama.generate(request)

4. 실제 활용 사례: 세 가지 현장 사례

이론을 충분히 살펴봤으니 실제로 겪었던 문제와 해결 방법을 보겠습니다.

4.1 사례 1: 8GB VRAM에서 13B 모델 안정적으로 실행하기

문제

RTX 3060(8GB VRAM)에서 Llama 2 13B Q4 모델을 실행하려고 합니다. 모델 자체에 약 8GB가 필요해 간신히 로드되지만, 몇 번 추론하면 KV cache가 VRAM을 소진해 OOM 오류가 발생합니다.

해결 방법

핵심은 KV cache 사용량과 최대 VRAM 사용량을 줄이는 것입니다.

FROM llama2:13b-q4

PARAMETER num_gpu 30      # 13B 모델의 전체 40개 계층 중 30개만 GPU에 올림
PARAMETER low_vram true   # 저용량 VRAM 모드를 켜고 KV cache를 CPU 메모리에 배치
PARAMETER num_ctx 2048    # 컨텍스트 길이를 절반으로 줄여 KV cache도 절반으로 축소
PARAMETER num_batch 256   # 배치를 줄여 최대 사용량 완화

이 매개변수를 조합하면 VRAM 사용량이 약 6GB로 안정되며, 사용량 변동에 대비해 2GB의 여유 공간을 남길 수 있습니다.

결과

6GB
안정적인 VRAM 사용량
Source: 8GB에서 6GB로 감소해 OOM 해소
  • VRAM 사용량: 약 8GB에서 약 6GB로 감소해 안정적으로 실행
  • 추론 속도: 약 8 tokens/s. 전체 GPU보다는 느리지만 CPU보다 훨씬 빠름
  • 안정성: OOM으로 더 이상 중단되지 않음

대신 추론 속도가 다소 느려집니다. 계층 10개를 CPU에서 계산해야 하고 GPU와 CPU를 오갈 때마다 데이터 전송 비용이 발생하기 때문입니다. 그래도 갑작스럽게 중단되지 않고 모델을 사용할 수 있습니다.

4.2 사례 2: 듀얼 GPU 부하 분산으로 처리량 높이기

문제

RTX 3090 두 장(각 24GB VRAM)을 갖춘 환경에서 외부 API 서비스를 제공하는 Ollama를 배포했습니다. 단일 인스턴스는 요청을 직렬로만 처리하므로 동시 처리 능력이 낮고, 사용량이 많은 시간에는 요청이 심하게 밀립니다.

nvidia-smi로 확인하면 두 카드의 사용률 차이가 큽니다. 한 장은 늘 70% 이상인데 다른 한 장은 20% 정도에 불과합니다.

해결 방법

앞의 2장에서 자세히 설명한 여러 인스턴스 + Nginx 부하 분산 구성을 사용합니다. 다음은 전체 시작 스크립트입니다.

#!/bin/bash
# start_ollama_cluster.sh

# 인스턴스 1 - GPU 0
CUDA_VISIBLE_DEVICES=0 \
OLLAMA_HOST=127.0.0.1:11434 \
OLLAMA_MODELS=/home/user/.ollama \
nohup ollama serve > ollama1.log 2>&1 &

# 인스턴스 2 - GPU 1
CUDA_VISIBLE_DEVICES=1 \
OLLAMA_HOST=127.0.0.1:11435 \
OLLAMA_MODELS=/home/user/.ollama \
nohup ollama serve > ollama2.log 2>&1 &

# 두 인스턴스에 모델 미리 로드
sleep 5
curl http://127.0.0.1:11434/api/pull -d '{"name": "llama3"}'
curl http://127.0.0.1:11435/api/pull -d '{"name": "llama3"}'

echo "Ollama cluster started on ports 11434 and 11435"

Nginx에는 요청이 고르게 분산되도록 least_conn 전략을 적용합니다.

결과

80%
처리량 향상
Source: 듀얼 인스턴스 병렬 처리와 단일 인스턴스 직렬 처리 비교
  • 전체 처리량: 단일 인스턴스 직렬 처리에서 듀얼 인스턴스 병렬 처리로 바꿔 약 80% 향상
  • GPU 한 장당 사용률: 평균 40%에서 평균 80%로 상승. 두 카드 모두 작업에 활용됨
  • 응답 지연: 사용량이 많은 시간에 대기 시간이 사라져 약 50% 감소

실측 결과 단일 인스턴스로 요청 100개를 처리하는 데 약 10분이 걸렸지만, 듀얼 인스턴스 부하 분산에서는 5분을 조금 넘겼습니다.

4.3 사례 3: VRAM 동적 할당 자동화

문제

크기가 다른 여러 모델을 전환할 때마다 GPU 계층 수를 직접 조정해야 합니다. 설정 변경을 잊으면 프로세스가 중단되기도 합니다. 이 과정을 자동화할 수 있을까요?

해결 방법

현재 VRAM 상태에 따라 적절한 Modelfile 구성을 자동으로 선택하는 스크립트를 작성합니다.

#!/bin/bash
# auto_offload.sh - GPU offloading 구성 자동화

# 현재 GPU 여유 VRAM 확인(단위: MB)
GPU_MEM_FREE=$(nvidia-smi --query-gpu=memory.free --format=csv,noheader,nounits | head -1)

# 모델 크기 참고값(MB)
declare -A MODEL_SIZES
MODEL_SIZES["llama3:8b-q4"]=5000
MODEL_SIZES["llama3:70b-q4"]=40000
MODEL_SIZES["mistral:7b-q4"]=4500

MODEL_NAME=$1

if [ -z "$MODEL_NAME" ]; then
    echo "Usage: $0 <model_name>"
    exit 1
fi

MODEL_SIZE=${MODEL_SIZES[$MODEL_NAME]}

if [ -z "$MODEL_SIZE" ]; then
    echo "Unknown model size for $MODEL_NAME"
    exit 1
fi

# 전체 GPU offloading이 가능한 VRAM인지 확인
if [ $GPU_MEM_FREE -gt $MODEL_SIZE ]; then
    # 전체 GPU offloading
    echo "Using full GPU offloading (enough memory)"
    cat > /tmp/modelfile_temp <<EOF
FROM $MODEL_NAME
PARAMETER num_gpu -1  # -1은 전체 GPU를 뜻함
PARAMETER low_vram false
EOF
else
    # 일부 GPU만 사용하고 적절한 계층 비율 계산
    OFFLOAD_RATIO=$((GPU_MEM_FREE * 100 / MODEL_SIZE))
    echo "Using partial GPU offloading ($OFFLOAD_RATIO%)"
    cat > /tmp/modelfile_temp <<EOF
FROM $MODEL_NAME
PARAMETER num_gpu $OFFLOAD_RATIO
PARAMETER low_vram true
PARAMETER num_ctx 2048
EOF
fi

# 모델 생성
ollama create "${MODEL_NAME}-auto" -f /tmp/modelfile_temp
echo "Created ${MODEL_NAME}-auto with auto config"

사용 방법은 다음과 같습니다.

# 스크립트를 실행해 환경에 맞는 구성의 모델 자동 생성
./auto_offload.sh llama3:70b-q4

결과

  • VRAM 변화에 자동 대응
  • 수동 구성 오류 감소
  • 모델을 바꿀 때마다 매개변수를 수정할 필요 없음

이 스크립트는 더 확장할 수 있습니다. 예를 들어 모니터링을 추가해 VRAM이 부족할 때 저용량 VRAM 모드로 자동 전환하거나, 예약 작업과 결합해 사용자가 없는 새벽 시간에 모델을 미리 로드할 수 있습니다.

5. 권장 사례와 모니터링: 추천 구성, 도구, 자주 묻는 문제

5.1 VRAM 용량별 권장 구성

하드웨어에 맞는 구성을 빠르게 찾을 수 있도록 표로 정리했습니다.

VRAM 용량권장 모델양자화GPU 계층 수기타 매개변수
6GB7B 모델Q4일부(약 50%)low_vram=true, ctx=2048
8GB7B 모델Q4전체 GPUctx=2048(안정성을 위한 설정)
8GB13B 모델Q4일부(약 75%)low_vram=true, ctx=2048, batch=256
12GB13B 모델Q4전체 GPUctx=4096 사용 가능
16GB13B 모델Q8 또는 Q5전체 GPUctx=4096
16GB70B 모델Q4일부(약 50%)low_vram=true
24GB70B 모델Q4전체 GPUctx=4096 사용 가능
48GB(듀얼 GPU)70B 모델Q4전체 GPU여러 인스턴스로 부하 분산

이 값은 보수적인 추정치입니다. 실제로는 KV cache와 시스템 예약 공간도 고려해야 합니다. 긴 대화처럼 컨텍스트가 큰 환경에서는 여유를 더 두는 편이 안정적입니다.

5.2 VRAM 모니터링 도구

nvidia-smi 실시간 모니터링

가장 간단한 방법은 다음과 같습니다.

# 1초마다 새로 고침
nvidia-smi -l 1

# VRAM 사용량만 확인
nvidia-smi --query-gpu=memory.used,memory.free --format=csv -l 1

출력에서 GPU별 VRAM 사용 상태를 확인할 수 있습니다. 추론 중에 지켜보면 VRAM이 어떻게 증가하는지 알 수 있습니다.

Ollama verbose 로그

ollama run llama3 --verbose

출력에는 모델 로드 과정의 상세 정보가 표시됩니다.

  • GPU offloading 계층 수
  • 모델 메모리 사용량
  • mmap 활성화 여부
  • KV cache 할당

GPU offloading: 40/40 layers와 같은 정보가 보이면 모든 모델 계층이 GPU에 올라갔다는 뜻입니다.

모니터링 스크립트 예시

VRAM 사용량을 장기간 모니터링하려면 다음과 같이 로그를 기록하는 스크립트를 작성할 수 있습니다.

#!/bin/bash
# monitor_gpu.sh

LOG_FILE="gpu_memory.log"

while true; do
    TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
    GPU_MEM=$(nvidia-smi --query-gpu=memory.used,memory.free --format=csv,noheader)
    echo "$TIMESTAMP $GPU_MEM" >> $LOG_FILE
    sleep 5
done

백그라운드에서 실행해 두면 언제든 과거 데이터를 확인할 수 있습니다.

5.3 자주 발생하는 문제 해결

문제 1: 추론 중 OOM 발생

다음 순서로 확인합니다.

  1. 먼저 nvidia-smi로 VRAM이 실제로 부족한지 확인
  2. 현재 구성 확인:
    • 양자화가 Q4인지 확인. 아니라면 Q4로 변경
    • 컨텍스트 길이가 너무 긴지 확인하고 2048로 변경
    • 배치가 너무 크다면 256으로 변경
    • 모든 계층을 GPU에 올렸다면 GPU 계층 수를 몇 개 줄임
  3. 위 설정을 모두 조정해도 해결되지 않으면 low_vram=true 활성화

조정 우선순위는 양자화 > ctx > batch > GPU 계층 수 > low_vram입니다.

문제 2: 추론 속도가 느림

먼저 GPU offloading 계층 수가 부족한지 확인합니다.

ollama run your_model --verbose | grep "GPU offloading"

GPU offloading: 20/40 layers가 표시되면 계층의 절반을 CPU에서 연산하고 있으므로 속도가 느린 것이 정상입니다.

해결하려면 양자화 수준을 낮추거나(Q4 → Q8) VRAM이 더 큰 그래픽 카드로 교체합니다. 이 방법을 사용할 수 없다면 현재 속도를 감수해야 합니다.

문제 3: VRAM 변동이 크고 불안정함

VRAM 변동은 주로 KV cache 때문에 발생합니다. 대화가 길어질수록 KV cache가 커집니다.

컨텍스트 길이를 제한하거나 애플리케이션 계층에서 대화 기록 길이를 제어합니다. 예를 들어 최근 대화 10개만 유지할 수 있습니다.

문제 4: 멀티 GPU 구성 후에도 GPU 한 장만 사용함

Nginx 구성이 적용됐는지 확인합니다.

curl http://localhost:8080/api/tags

모델 응답이 하나만 표시된다면 요청이 실제로 분산되고 있다는 뜻입니다.

두 GPU의 사용률 차이가 크다면 다음 원인을 확인합니다.

  • least_conn 전략을 설정하지 않음
  • 특정 인스턴스에 문제가 생김. 로그 확인 필요
  • 모델이 한 인스턴스에만 로드됨

정리

핵심은 다음 네 가지입니다.

  1. VRAM이 부족하면 양자화를 먼저 조정: Q4는 FP16보다 VRAM을 75% 절약하면서 성능 손실은 크지 않음
  2. KV cache 사용량 확인: 컨텍스트 길이가 KV cache에 직접 영향을 주며, 긴 대화는 VRAM 부담을 높임
  3. 멀티 GPU에는 부하 분산 사용: 단일 인스턴스 멀티 GPU 방식은 활용도가 낮으며, 여러 인스턴스 + Nginx 구성이 효과적임
  4. llama.cpp 내부 동작 이해: GPU offloading은 계층 단위 연산이며 데이터 전송 비용이 발생함

바로 사용할 수 있는 구성은 다음과 같습니다.

8GB VRAM 안정 구성:

PARAMETER num_gpu 30
PARAMETER low_vram true
PARAMETER num_ctx 2048
PARAMETER num_batch 256

듀얼 GPU 부하 분산 시작 명령:

CUDA_VISIBLE_DEVICES=0 OLLAMA_HOST=127.0.0.1:11434 ollama serve &
CUDA_VISIBLE_DEVICES=1 OLLAMA_HOST=127.0.0.1:11435 ollama serve &

이 글이 도움이 됐다면 시리즈의 다른 글도 살펴보세요. 여섯 번째 글에서는 양자화와 배치의 기초를 다루고, 이번 글은 GPU 활용법을 더 깊이 설명합니다. 여덟 번째 글에서는 여러 모델의 병렬 배포와 멀티 GPU를 더 복잡한 환경에 적용하는 방법을 다룹니다.

궁금한 점이 있다면 Ollama GitHub Discussions에서 검색해 보세요. 실제 문제에 관한 커뮤니티 논의를 많이 찾을 수 있습니다. 댓글을 남겨도 확인하는 대로 답변하겠습니다.


FAQ

Ollama는 하나의 모델을 여러 GPU로 나눠 병렬 연산할 수 있나요?
불가능합니다. Ollama는 모델 병렬화(Tensor Parallelism)를 지원하지 않으며, 각 모델 인스턴스는 GPU 한 장에만 연결할 수 있습니다. 여러 GPU를 활용하려면 여러 인스턴스를 실행하고 Nginx로 부하를 분산해야 합니다.
모델은 정상적으로 로드됐는데 몇 번 추론한 뒤 OOM이 발생하는 이유는 무엇인가요?
KV cache 때문입니다. 모델을 로드할 때는 모델 자체가 차지하는 VRAM만 계산하지만, 추론 중에는 대화 길이에 따라 KV cache가 커집니다. 권장 방법:

• 컨텍스트 길이(num_ctx) 줄이기
• low_vram 모드 활성화
• 대화 기록 줄이기
VRAM이 부족할 때 어떤 매개변수부터 조정해야 하나요?
우선순위는 양자화 > 컨텍스트 길이 > 배치 크기 > GPU 계층 수 > low_vram입니다. 양자화의 영향이 가장 크며, Q4는 VRAM을 75% 절약하면서 성능 손실은 2~3%에 그칩니다.
num_gpu 매개변수는 보유한 GPU 개수를 뜻하나요?
아닙니다. num_gpu는 모델 계층 중 GPU에서 연산할 계층 수를 뜻합니다. 예를 들어 32계층 모델에서 num_gpu=32는 전체 GPU 연산, num_gpu=20은 20개 계층을 GPU에서, 나머지 12개 계층을 CPU에서 연산한다는 의미입니다.
멀티 GPU 부하 분산에는 어떤 전략이 적합한가요?
least_conn(최소 연결 우선)을 권장합니다. 추론 요청마다 처리 시간이 달라 라운드 로빈을 쓰면 한 인스턴스는 과부하가 걸리고 다른 인스턴스는 유휴 상태가 될 수 있습니다. least_conn은 현재 연결 수가 가장 적은 인스턴스로 요청을 보냅니다.
8GB VRAM으로 어느 정도 크기의 모델을 실행할 수 있나요?
보수적인 구성은 다음과 같습니다.

• 7B Q4: 전체 GPU, ctx=2048
• 13B Q4: 일부 GPU(약 75%), low_vram + ctx=2048 + batch=256 필요
• 더 큰 모델은 더 많은 VRAM 또는 CPU offloading 필요

5분 읽기 · 게시일: 2026년 4월 11일 · 수정일: 2026년 9월 4일

댓글

GitHub로 로그인하여 댓글을 남기세요

Easton BlogEaston Blog