본문으로 건너뛰기
페이지 목차
이 페이지에서 검색

로컬 LLM

이 페이지에 묻기

로컬 LLM · 현재 차시와 목차 본문에서만 찾아 답합니다. 답을 지어내지 않고 해당하는 대목을 그대로 보여 주며, 여기에 없으면 없다고 알려 드립니다.

생성형 AI가 아니라 이 페이지 본문을 찾아 그대로 보여 주는 방식입니다. 외부로 나가는 요청이 없습니다.

LOCAL · LLM

로컬 LLM

생성 수정

로컬 모델 실행과 GPU·메모리 이해부터 서빙·RAG·도구 호출·업무형 앱 구현까지 차례로 살펴봅니다.

42개 문서 · 5일차 · 36 / 42

35차시 · LLM Serving 구축

지도 위치 : 5일차 · 프로젝트 3단계 · ③추론 엔진 + ④API 층 | 이전: 34차시 LLM 모델 선정 및 최적화 | 다음: 36차시 AI 기능 구현 키워드 : vLLM 기반 모델 서버 구축, API 구성 및 테스트

이 차시를 다 읽으면

  1. vllm serve 명령의 옵션마다 34차시 결정 기록의 어느 줄이 근거인지 연결할 수 있다.
  2. 기동 로그에서 최대 동시성 줄을 찾아 33차시의 동시 사용자 요구와 대조할 수 있다.
  3. 바인딩 주소 와 API 키 가 각각 무엇을 막는지, 무엇은 막지 못하는지 말할 수 있다.
  4. 모델 서버를 systemd 또는 Docker 로 상시 실행하는 이유와 차이를 말할 수 있다.
  5. /health · /v1/models · 채팅 호출 · 실패 시험 으로 서버를 검수할 수 있다.
  6. 개발 중에는 Ollama로 바꿔 끼우는 방법과 그때 달라지는 점을 말할 수 있다.

0. 이 차시의 판별 축

#축왼쪽 (0)오른쪽 (1)이 축으로 갈리는 것큰 축 (README 5장)
1몇 명에게 제공하나1인 (Ollama)다수 동시 (vLLM)운영 엔진의 선택⑤ 하나 ↔ 여럿
2상주하는가터미널에서 띄운 프로세스OS · 컨테이너 엔진이 관리하는 서비스재부팅 · 장애 후 다시 뜨는가이 차시의 축 (5절)
3누구에게 열려 있나127.0.0.1 (이 장비 안에서만)0.0.0.0 (모든 인터페이스)모델 서버가 사내망 · 외부에 노출되는가④ 로컬 ↔ 원격
4계약인가 구현인가OpenAI 호환 API (포트)vLLM · Ollama (어댑터)엔진을 바꿔도 앱은 그대로인가② 추상 ↔ 구상

앞에서 이어집니다 — 3일차(17~20차시)에서 vLLM 서버를 띄우고 불러 보았습니다. 35차시는 같은 일을 운영할 수 있게 합니다. 차이는 세 가지입니다. 옵션마다 근거 가 있고(34차시), 아무나 부를 수 없고 (4절), 누가 보지 않아도 계속 떠 있습니다 (5절). 앞에서 배운 Ollama와 vLLM 비교(24차시) — 같은 엔진 층, 다른 목적(1인 실행 ↔ 다수 동시 서빙) — 에서 오른쪽 칸을 고른 결과입니다.


1. 3일차를 서버 구축 절차로 회수하기

단계하는 일회수하는 앞 차시이 단계의 산출물
① 격리vLLM 전용 가상환경 (버전 고정)3 · 18차시환경 설치 기록 (vllm --version 출력)
② 명령옵션마다 근거를 붙여 기동 명령 결정19차시, 34차시기동 명령 + 근거표 (2절)
③ 로그기동 로그에서 메모리 · 최대 동시성 확인19 · 21차시로그 발췌 (3절)
④ 보안바인딩 주소 · API 키20차시보안 설정표 (4절)
⑤ 상주systemd 또는 Docker—서비스 파일 (5절)
⑥ 시험정상 시험 + 실패 시험20차시API 테스트 체크리스트 (7절)

⚠️ ①을 건너뛰면 안 됩니다. 앞에서 배운 대로 vLLM은 특정 PyTorch 버전을 고정해서 설치합니다(18차시). 36차시 이후의 앱 코드와 같은 환경에 넣으면 의존성이 서로 밀어냅니다.


2. 기동 명령 결정 — 옵션마다 34차시 근거

vllm serve <34차시 결정 기록의 모델 저장소> \
  --served-model-name munuibot \
  --host 127.0.0.1 --port 8000 \
  --max-model-len 4096 \
  --gpu-memory-utilization 0.9 \
  --max-num-seqs 16 \
  --api-key "$VLLM_API_KEY" \
  --enable-auto-tool-choice --tool-call-parser hermes

🔍 읽는 포인트 : 첫 줄의 모델 저장소, --max-model-len, (썼다면) --kv-cache-dtype은 34차시 DR-001에서 그대로 옮겨 온 값 입니다. 여기서 새로 정하는 값이 아닙니다.

옵션값 (문의봇)근거값이 틀리면
모델 저장소DR-001의 결정34차시 매트릭스평가하지 않은 모델이 운영됨
--served-model-namemunuibot앱이 부르는 이름을 실제 모델과 분리 (축 4)모델을 바꿀 때마다 앱 코드도 바꿔야 함
--max-model-len4096DR-001 · 평가셋 최장 메일크면 동시성 ↓, 작으면 긴 메일 잘림
--gpu-memory-utilization0.934차시 예산표 (24GB × 0.9)같은 GPU에 다른 프로세스가 있으면 기동 실패
--max-num-seqs1633차시 NFR-01 (동시 10명) 이상 여유너무 작으면 11번째 요청부터 대기
--quantization대개 생략사전 양자화 모델은 대개 자동 인식인식됐는지 기동 로그로 확인
--kv-cache-dtype fp8DR-001에서 정했을 때만34차시 예산표 C안품질 영향 가능 → 39차시 재확인
--enable-auto-tool-choice --tool-call-parser hermes켬38차시 주문 조회 도구도구 호출이 글로만 나옴. 파서 이름은 모델별로 다르니 문서 확인
--host · --api-key4절33차시 NFR-02 (보안)4절

💡 프리픽스 캐싱 — 문의봇의 모든 요청은 같은 시스템 프롬프트 로 시작합니다. 앞부분 KV를 재사용하면 TTFT가 줄어듭니다(21차시). --enable-prefix-caching은 최신 버전에서 기본으로 켜져 있을 수 있으니 기동 로그에서 확인 하고, 꺼져 있다면 켭니다.

⭐ 옵션 하나를 바꾸면 결정 기록과 어긋납니다. 명령을 고치면 DR-001도 고칩니다. 그래야 40차시에 "왜 이 설정인가"를 답할 수 있습니다.


3. 기동 로그 읽기 — 최대 동시성

기동 로그에서 볼 줄은 세 개입니다. 문구와 형식은 버전에 따라 다르니 비슷한 줄을 찾습니다.

... Loading weights took ... GiB                                   ← 가중치 메모리
... KV cache ... GiB / # GPU blocks ...                            ← KV 캐시에 할당된 메모리
... Maximum concurrency for 4096 tokens per request: NN.NNx        ← 최대 동시성
로그 줄이걸로 아는 것대조할 곳
가중치 메모리양자화가 실제로 적용됐는가34차시 매트릭스의 VRAM
KV 캐시 할당예산표의 KV 칸이 실제로 얼마인가34차시 예산표 2.3절
Maximum concurrency요청마다 max-model-len을 꽉 채워도 동시에 몇 개가 KV 캐시에 들어가는가33차시 NFR-01 (동시 10명)

⭐ 판정은 한 줄입니다. 최대 동시성 ≥ 10 이면 다음 단계로, < 10 이면 34차시로 돌아가 예산표의 칸을 다시 나눕니다.

⚠️ 이 숫자를 잘못 읽는 두 가지

❌ 오해⭕ 실제
"12x니까 12명까지 빠르다 "KV 캐시에 들어간다 는 뜻일 뿐. 빠른지는 39차시에 잽니다 (한 건의 시간 ↔ 전체 처리량)
"12x니까 13번째 요청은 거절된다"실제 요청은 대개 4096보다 짧아 더 들어갑니다. 넘치면 거절이 아니라 대기 · 선점(preemption) 이 일어납니다(21차시)

⚠️ 요청이 하나도 없는데 nvidia-smi가 거의 꽉 차 보이는 것은 정상 입니다. --gpu-memory-utilization만큼 기동할 때 미리 확보 하기 때문입니다(19차시).


4. 보안 — 바인딩 주소와 API 키

4.1 바인딩 주소

바인딩 주소 (Bind Address) — [서버 설정]인데, [ 어느 네트워크 인터페이스로 들어오는 연결을 받을지 정한다 ]는 점에서 기계 안의 창구 번호를 정하는 포트 번호와 구별된다.

설정누가 부를 수 있나문의봇에서 언제
--host 127.0.0.1이 장비 안의 프로그램만앱 서버가 같은 장비 에 있을 때 ⭕ 기본값으로 삼기
--host <사내 IP>그 인터페이스로 오는 사내망앱 서버가 다른 장비 에 있을 때. 방화벽으로 앱 서버만 허용
--host 0.0.0.0모든 인터페이스컨테이너 안 에서만 (5.2절). 맨 서버에서는 ⚠️

앞에서 배운 OLLAMA_HOST=0.0.0.0의 위험(5차시 4절) 과 같은 문제입니다. 0.0.0.0은 "이 장비의 모든 네트워크 창구로 들어오는 연결을 받는다"는 뜻이고, 127.0.0.1은 "이 장비 자신에게서 온 연결만 받는다"는 뜻입니다. 모델 서버는 앱 서버만 부르면 됩니다. 상담원 브라우저가 모델 서버를 직접 부를 이유는 없습니다.

[상담원 브라우저] ──▶ [앱 서버 :8080] ──▶ [vLLM 127.0.0.1:8000]
                       (37·38차시)          (밖에서는 안 보임)

4.2 API 키 — 인증은 하지만 인가는 하지 않는다

--api-key를 주면 요청 헤더 Authorization: Bearer <키>가 맞지 않을 때 401 을 돌려줍니다. 이것이 인증 의 가장 단순한 형태입니다.

묻는 질문문의봇에서
인증 (Authentication, AuthN)너는 누구냐? — 부른 쪽의 신원을 확인API 키가 맞는가 → 우리 앱 서버다
인가 (Authorization, AuthZ)너는 이것을 해도 되나? — 확인된 신원에 허락된 일인지 판정상담원 A가 이 주문을 볼 권한이 있는가

인증이 먼저, 인가가 그다음입니다. 신원을 확인했다고 무엇이든 해도 되는 것은 아닙니다.

API 키가 막는 것API 키가 막지 못하는 것
키를 모르는 프로그램의 호출키를 아는 앱 서버가 어떤 상담원을 대신해 무엇을 부르는지
실수로 열린 포트로 들어온 호출상담원 A가 상담원 B의 고객 주문을 조회하는 것 → 인가(AuthZ) , 38차시

⚠️ 키를 명령줄에 그대로 쓰면 셸 기록과 프로세스 목록에 남습니다. 앞에서 배운 .env 규칙(20차시 3.3) — 실제 값은 코드 저장소(Git)에 올리지 않는다 — 대로 환경 파일 에 두고 읽게 합니다.

⚠️ 어떤 경로까지 키를 요구하는지(예: /health, /metrics)는 버전에 따라 다를 수 있습니다. 추측하지 말고 7절 시험 4~5번으로 직접 확인합니다.


5. 상시 실행 — systemd 또는 Docker

상시 실행 서비스 — [프로세스 운영 방식]인데, [ 터미널을 닫거나 서버가 재부팅되거나 프로세스가 죽어도 OS나 컨테이너 엔진이 다시 띄운다 ]는 점에서 사람이 터미널에서 직접 띄운 프로세스와 구별된다.

0절 축 2의 오른쪽 칸 입니다. 리눅스에서 이 일을 하는 관리자가 systemd 입니다 — 프로그램을 "서비스"로 등록해 두면 부팅할 때 띄우고, 죽으면 다시 띄우고, 로그를 모아 줍니다. 다른 길은 Docker 입니다 — 프로그램과 그 실행 환경(라이브러리 · 설정)을 이미지 라는 한 덩어리로 묶고, 그 이미지를 실행한 것( 컨테이너 )을 Docker 엔진이 관리합니다. Ollama는 Linux 설치 스크립트가 이미 systemd 서비스로 등록 해 주었습니다(5차시). vLLM은 직접 등록합니다.

5.1 systemd

# /etc/systemd/system/munuibot-vllm.service
[Unit]
Description=munuibot vLLM server
After=network-online.target

[Service]
User=llm
EnvironmentFile=/etc/munuibot/vllm.env
ExecStart=/opt/munuibot/vllm-venv/bin/vllm serve ${MODEL_REPO} --served-model-name munuibot --host 127.0.0.1 --port 8000 --max-model-len 4096 --api-key ${VLLM_API_KEY}
Restart=on-failure

[Install]
WantedBy=multi-user.target

🔍 읽는 포인트 : EnvironmentFile (키는 파일에서), ExecStart의 가상환경 안 vllm 경로 (1절 ①), Restart=on-failure (죽으면 다시). 등록은 sudo systemctl enable --now munuibot-vllm, 로그는 journalctl -u munuibot-vllm -f.

5.2 Docker

docker run -d --name munuibot-vllm --gpus all --ipc=host \
  -p 127.0.0.1:8000:8000 \
  -v ~/.cache/huggingface:/root/.cache/huggingface \
  --env-file vllm.env \
  --restart unless-stopped \
  vllm/vllm-openai:<고정한 버전 태그> \
  --model <모델 저장소> --served-model-name munuibot --max-model-len 4096

🔍 읽는 포인트 : -p 127.0.0.1:8000:8000 — 컨테이너는 호스트와 네트워크가 분리된 작은 별도 컴퓨터처럼 동작합니다. 그래서 컨테이너 안 의 서버가 127.0.0.1로만 열리면 컨테이너 밖(호스트)에서 들어오는 연결조차 받지 못하므로, 안쪽은 0.0.0.0으로 열어야 합니다. 대신 호스트 쪽 은 127.0.0.1에만 묶습니다. -v — 모델 파일(수 GB)을 컨테이너 밖 디스크에 두어 다시 받지 않게 합니다(축 ① 디스크 ↔ 메모리). 이미지 인자 형식과 권장 옵션은 이미지 버전의 공식 문서로 확인 합니다.

5.3 무엇을 고르나

systemd (가상환경 직접)Docker
장점층이 하나 적음. 로그가 journalctl로 바로환경 통째로 고정 · 이동 쉬움
단점서버마다 설치 반복GPU를 컨테이너에 넘기는 설정 필요
어울리는 곳장비 한 대, 하루 프로젝트같은 구성을 여러 장비에

⚠️ 어느 쪽이든 버전을 고정 합니다. :latest(그때그때 가장 새 버전을 가리키는 이름표)만 쓰면, 어제와 오늘 받은 이미지가 다를 수 있고 문제가 생겼을 때 되돌아갈 대상이 없습니다.


6. 개발 중엔 Ollama로 바꿔 끼우기

앞에서 배운 20차시 의 핵심 — 앱은 OpenAI 호환 API(포트) 만 알고, 뒤가 Ollama인지 vLLM인지 모른다 — 을 프로젝트에서 씁니다. GPU가 하나뿐이라 vLLM이 메모리를 다 잡고 있을 때, 노트북에서 앱만 개발할 때 유용합니다.

설정 (앱의 .env)개발 (Ollama)운영 (vLLM)
LLM_BASE_URLhttp://localhost:11434/v1http://127.0.0.1:8000/v1
LLM_MODELqwen3:8b (또는 Modelfile로 만든 이름)munuibot
LLM_API_KEY아무 값환경 파일의 실제 키

⚠️ 바꿔 끼워도 같지 않은 것

다른 점왜대응
컨텍스트 길이Ollama 기본 num_ctx는 작게 잡혀 있을 수 있어 긴 메일이 조용히 잘림Modelfile PARAMETER num_ctx 설정 (13차시)
양자화 형식GGUF ↔ AWQ 등. 품질이 다를 수 있음최종 평가는 운영 형식으로 (34차시 4.2)
동시 처리Ollama는 1인 사용 중심성능 측정은 vLLM에서만 (39차시)
도구 호출 형식엔진 · 모델마다 지원이 다름38차시 통합 시험은 운영 엔진으로

⭐ README 7장 문장 3 — "표준 API로 층을 나누면 엔진을 갈아 끼울 수 있다" — 의 실제 쓰임입니다. 단, 갈아 끼울 수 있다는 것은 코드가 같다 는 뜻이지 결과가 같다 는 뜻이 아닙니다.


7. 실습 — 맡기고 검수하기

지시

문의봇의 vLLM 모델 서버를 systemd 서비스로 구축하고 API 시험을 해줘.

목표: 34차시 결정 기록(DR-001, 아래 첨부)대로 vLLM 서버를 상시 실행하고, 아래 시험표를 전부 실행.
맥락: Ubuntu, RTX 4090 24GB, vLLM 전용 가상환경 /opt/munuibot/vllm-venv. vLLM 공식 문서의 serve 옵션 페이지를 기준으로.
제약:
- 바인딩은 127.0.0.1, API 키는 /etc/munuibot/vllm.env 에서 읽게. 키 값을 보고서에 쓰지 마.
- 옵션마다 DR-001의 어느 항목이 근거인지 표로 달아. 근거 없는 옵션을 추가하지 마.
- sudo가 필요한 명령(서비스 등록 등)은 실행 전에 나에게 먼저 보여줘.
- 추측하지 말고 실행 명령과 출력 원문을 붙여.
보고 형식:
1) [옵션 | 값 | 근거(DR-001 항목)] 표
2) 기동 로그 중 가중치 메모리 · KV 캐시 · Maximum concurrency 줄 원문
3) [# | 시험 | 명령 | 기대 | 실제(출력 원문 요약) | 통과] 표

받을 증거

  • 서비스 파일 전문, systemctl status 출력
  • 기동 로그의 Maximum concurrency 줄 원문
  • 시험표 (아래) 각 행의 실행 명령과 출력

API 테스트 체크리스트

#시험명령 (예)기대
1살아 있나curl -i http://127.0.0.1:8000/health200
2모델 이름curl -s .../v1/models -H "Authorization: Bearer $KEY"munuibot이 목록에
3채팅 한 번/v1/chat/completions, 짧은 문의 하나content 있음, finish_reason: "stop", usage 숫자
4키 없이3번에서 헤더 빼기401
5틀린 키3번에서 키 바꾸기401
6스트리밍3번 + "stream": true, curl -Ndata: {...} 조각들, 끝에 data: [DONE]
7너무 긴 입력max-model-len을 넘는 메일에러 응답 (조용히 잘리지 않음)
8다른 장비에서다른 PC에서 curl http://<서버IP>:8000/health연결 거부
9프로세스가 죽으면sudo systemctl kill -s SIGKILL munuibot-vllm 후 대기다시 뜸 (Restart=on-failure)
10재부팅 후재부팅 → 1번200

내가 직접 볼 것

볼 것이걸로 아는 것 / 왜 내가 보는가
Maximum concurrency 숫자 ≥ 1033차시 NFR-01을 KV 캐시가 감당하는가. 미달이면 34차시로
시험 4 · 5 · 8번 이 실패해야 할 때 실패하는가정상 시험만 통과한 서버는 시험을 안 한 서버와 같습니다. "막혀야 할 것이 막히는가"를 보지 않으면, 문이 열려 있어도 모릅니다
시험 7번의 응답긴 메일이 잘린 채 답이 나오면 가장 위험. 에러여야 앱이 대응합니다
보고서에 API 키 값 이 없는가키가 보고서 · 로그에 찍히면 그 자체가 유출입니다

산출물 체크리스트 (35차시)

  • vLLM 전용 가상환경 + vllm --version 기록
  • 기동 명령 + 옵션 근거표 (DR-001 항목과 연결)
  • 기동 로그 발췌 (가중치 · KV 캐시 · 최대 동시성)
  • 보안 설정: 바인딩 127.0.0.1, API 키는 환경 파일
  • systemd 서비스 파일 (또는 Docker 실행 명령, 버전 고정)
  • API 테스트 체크리스트 10항목 결과
  • 앱용 .env 두 벌 (개발 Ollama / 운영 vLLM)

부록 A. 스스로 확인하기

#질문답이 있는 곳
1--max-model-len의 값은 어디서 와야 하는가? 크거나 작으면 무엇이 생기나?2
2--served-model-name을 따로 주는 이유는? 어느 축인가?2
3Maximum concurrency 숫자로 판정하는 것과 판정하지 못하는 것은?3
4바인딩 주소를 유와 종차로 정의하면?4.1
5API 키는 인증 · 인가 중 무엇을 하는가? 막지 못하는 것은?4.2
6Docker에서 컨테이너 안은 0.0.0.0인데 호스트 쪽을 127.0.0.1로 묶는 이유는?5.2
7개발용 Ollama로 바꿔 끼웠을 때 달라지는 네 가지는?6
8실패해야 통과인 시험 세 가지는?7

부록 B. 자주 하는 실수

실수바로잡기
"일단 --host 0.0.0.0으로 열고 나중에 막자"❌ 열린 순간 사내망 누구나 부를 수 있습니다. 127.0.0.1부터
"API 키를 걸었으니 권한 문제는 끝"❌ 키는 인증 뿐. 상담원별 인가 는 앱 서버(38차시)
"기동 로그에 12x가 나왔으니 성능 요구 충족"❌ 메모리에 들어간다 는 뜻. 속도는 39차시에서
"nvidia-smi가 꽉 찼다, 메모리 누수다"⚠️ --gpu-memory-utilization만큼 미리 확보 한 것. 정상
"터미널에서 vllm serve 띄워 두면 된다"❌ 터미널을 닫거나 재부팅하면 사라집니다. 서비스로
"Ollama에서 되니 vLLM에서도 똑같다"⚠️ 코드는 같아도 결과는 다를 수 있습니다. 컨텍스트 · 형식 · 동시성
"정상 호출이 되니 시험 끝"❌ 키 없이 · 틀린 키 · 다른 장비 · 긴 입력의 실패 시험 까지

부록 C. NotebookLM 복습 프롬프트

이 35차시 노트만 근거로 6문제를 내줘. 비전공자 기준으로.
"옵션마다 34차시 근거 연결", "Maximum concurrency 읽는 법", "바인딩 주소와 API 키가 막는 것과 못 막는 것", "실패 시험"은 반드시 포함해줘.
답은 내가 답한 다음에 채점하고, 틀린 이유를 노트의 절 번호를 근거로 설명해줘.