From f1890713a74e09c89605c73bdef03e19adc3f67c Mon Sep 17 00:00:00 2001 From: MyeonghoeLee Date: Thu, 26 Mar 2026 15:07:21 +0900 Subject: [PATCH] =?UTF-8?q?vLLM=20=EC=A7=80=EC=9B=90=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=20=EB=B0=8F=20=ED=86=B5=ED=95=A9=20=EC=85=8B=EC=97=85/?= =?UTF-8?q?=EC=A2=85=EB=A3=8C=20=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - setup.sh: 시스템 자동 감지 (Apple Silicon/NVIDIA GPU/RAM) → 최적 방식 선택 - stop.sh: 실행 중인 서비스 자동 감지 후 종료 - setup-vllm.sh / stop-vllm.sh: NVIDIA GPU + Docker 기반 vLLM 서빙 - docker-compose.vllm.yml 자동 생성 (vLLM + Open WebUI) - README 전면 개편 Co-Authored-By: Claude Opus 4.6 (1M context) --- README.md | 107 ++++++++++++++++++-------------- setup-vllm.sh | 166 ++++++++++++++++++++++++++++++++++++++++++++++++++ setup.sh | 129 +++++++++++++++++++++++++++++++++++++++ stop-vllm.sh | 25 ++++++++ stop.sh | 75 +++++++++++++++++++++++ 5 files changed, 457 insertions(+), 45 deletions(-) create mode 100755 setup-vllm.sh create mode 100755 setup.sh create mode 100755 stop-vllm.sh create mode 100755 stop.sh diff --git a/README.md b/README.md index dcf261b..fa85e32 100644 --- a/README.md +++ b/README.md @@ -1,53 +1,19 @@ # Qwen3.5 로컬 서빙 + Open WebUI -로컬 Mac에서 Qwen3.5-35B 모델을 서빙하고, Open WebUI로 채팅할 수 있는 환경을 원클릭으로 구축합니다. +Qwen3.5-35B 모델을 로컬에서 서빙하고, Open WebUI로 채팅할 수 있는 환경을 원클릭으로 구축합니다. 텍스트 대화 + 이미지 입력 모두 지원합니다. --- -## 방식 선택 - -| | Ollama | MLX (vllm-mlx) | -|---|---|---| -| 장점 | 간편, 설정 적음 | Mac GPU 최적화, 파라미터 직접 제어 | -| 이미지 입력 | O | O | -| 환경 | Homebrew + Docker | Python venv + Docker | -| 셋업 | `./setup-ollama.sh` | `./setup-mlx.sh` | -| 종료 | `./stop-ollama.sh` | `./stop-mlx.sh` | - ---- - -## 사전 요구사항 - -- **Mac** (MLX 방식은 Apple Silicon 필수) -- **Docker Desktop** 설치 및 실행 -- **Homebrew** (Ollama 방식) -- **Python 3.10+** (MLX 방식) -- **RAM 32GB 이상** 권장 (4bit 모델 기준 ~20GB 사용) - ---- - -## 사용법 - -### Ollama 방식 +## 빠른 시작 ```bash -# 셋업 (Ollama 설치 → 모델 다운로드 → Open WebUI 실행) -./setup-ollama.sh +# 시스템 환경을 자동 감지하여 최적의 방식을 선택합니다 +./setup.sh -# 종료 -./stop-ollama.sh -``` - -### MLX 방식 - -```bash -# 셋업 (venv → vllm-mlx 설치 → 패치 → Open WebUI → 서버 시작) -./setup-mlx.sh - -# 종료 -./stop-mlx.sh +# 종료 (실행 중인 서비스를 자동 감지하여 종료) +./stop.sh ``` 셋업 완료 후 **http://localhost:3000** 접속 @@ -56,17 +22,68 @@ --- +## 자동 감지 기준 + +| 조건 | 선택 | 이유 | +|------|------|------| +| NVIDIA GPU 있음 | **vLLM** | CUDA 가속, 가장 빠름 | +| Apple Silicon + RAM 32GB↑ | **MLX** | Mac GPU 최적화 | +| Apple Silicon + RAM 부족 | **Ollama** | 메모리 관리 우수 | +| 그 외 | **Ollama** | 범용, CPU에서도 동작 | + +--- + +## 방식별 비교 + +| | Ollama | MLX (vllm-mlx) | vLLM | +|---|---|---|---| +| 환경 | Mac / Linux | Apple Silicon Mac | NVIDIA GPU (Linux) | +| 장점 | 간편, 설정 적음 | Mac GPU 최적화 | CUDA 가속, 고성능 | +| 이미지 입력 | O | O | O | +| 개별 셋업 | `./setup-ollama.sh` | `./setup-mlx.sh` | `./setup-vllm.sh` | +| 개별 종료 | `./stop-ollama.sh` | `./stop-mlx.sh` | `./stop-vllm.sh` | + +--- + +## 사전 요구사항 + +**공통:** +- Docker Desktop (또는 Docker Engine) 설치 및 실행 + +**Ollama:** +- Homebrew + +**MLX:** +- Apple Silicon Mac (M1/M2/M3/M4) +- Python 3.10+ +- RAM 32GB 이상 권장 + +**vLLM:** +- NVIDIA GPU (VRAM 20GB 이상 권장) +- nvidia-container-toolkit + +--- + ## 파일 구조 ``` -├── setup-ollama.sh # Ollama 원클릭 셋업 -├── setup-mlx.sh # MLX 원클릭 셋업 +├── setup.sh # 통합 셋업 (자동 감지) +├── stop.sh # 통합 종료 (자동 감지) +│ +├── setup-ollama.sh # Ollama 셋업 +├── setup-mlx.sh # MLX 셋업 +├── setup-vllm.sh # vLLM 셋업 +│ ├── stop-ollama.sh # Ollama 종료 ├── stop-mlx.sh # MLX 종료 -├── docker-compose.yml # Ollama용 Open WebUI -├── docker-compose.mlx.yml # MLX용 Open WebUI +├── stop-vllm.sh # vLLM 종료 +│ +├── docker-compose.yml # Ollama용 +├── docker-compose.mlx.yml # MLX용 +├── docker-compose.vllm.yml # vLLM용 (자동 생성) +│ ├── SETUP_OLLAMA.md # Ollama 상세 가이드 -└── SETUP_MLX.md # MLX 상세 가이드 (파라미터, 트러블슈팅) +└── SETUP_MLX.md # MLX 상세 가이드 ``` --- diff --git a/setup-vllm.sh b/setup-vllm.sh new file mode 100755 index 0000000..82ab77d --- /dev/null +++ b/setup-vllm.sh @@ -0,0 +1,166 @@ +#!/bin/bash +set -e + +#==================================================================== +# Qwen3.5 + Open WebUI (vLLM + NVIDIA GPU) 원클릭 셋업 +# 환경: Linux / NVIDIA GPU / Docker +#==================================================================== + +PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)" +MODEL="Qwen/Qwen3.5-35B-A3B" +PORT=8090 +WEBUI_PORT=3000 +MAX_TOKENS=8192 +MAX_MODEL_LEN=8192 + +echo "============================================" +echo " Qwen3.5 + Open WebUI (vLLM) 셋업" +echo "============================================" +echo "" + +#-------------------------------------------------------------------- +# 1. 사전 요구사항 확인 +#-------------------------------------------------------------------- +echo "[1/4] 사전 요구사항 확인..." + +# Docker +if ! command -v docker &>/dev/null; then + echo "❌ Docker가 설치되어 있지 않습니다." + exit 1 +fi +if ! docker info &>/dev/null; then + echo "❌ Docker 데몬이 실행 중이 아닙니다." + exit 1 +fi +echo " ✓ Docker" + +# NVIDIA GPU +if ! command -v nvidia-smi &>/dev/null; then + echo "❌ nvidia-smi를 찾을 수 없습니다. NVIDIA 드라이버가 설치되어 있는지 확인하세요." + exit 1 +fi + +GPU_NAME=$(nvidia-smi --query-gpu=name --format=csv,noheader | head -1) +GPU_VRAM=$(nvidia-smi --query-gpu=memory.total --format=csv,noheader,nounits | head -1) +echo " ✓ GPU: $GPU_NAME (${GPU_VRAM}MB)" + +if [ "$GPU_VRAM" -lt 20000 ]; then + echo " ⚠️ VRAM이 20GB 미만입니다. 4bit 양자화 모델을 사용하세요." +fi + +# nvidia-container-toolkit +if ! docker run --rm --gpus all nvidia/cuda:12.0.0-base-ubuntu22.04 nvidia-smi &>/dev/null; then + echo "❌ nvidia-container-toolkit이 설치되어 있지 않습니다." + echo " 설치: https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html" + exit 1 +fi +echo " ✓ nvidia-container-toolkit" + +echo "" + +#-------------------------------------------------------------------- +# 2. Docker Compose 설정 +#-------------------------------------------------------------------- +echo "[2/4] Docker Compose 설정..." + +if [ ! -f "$PROJECT_DIR/docker-compose.vllm.yml" ]; then + cat > "$PROJECT_DIR/docker-compose.vllm.yml" << EOF +services: + vllm: + image: vllm/vllm-openai:latest + container_name: vllm-server + ports: + - "${PORT}:8000" + volumes: + - vllm-models:/root/.cache/huggingface + environment: + - HUGGING_FACE_HUB_TOKEN=\${HUGGING_FACE_HUB_TOKEN:-} + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: [gpu] + command: > + --model ${MODEL} + --max-model-len ${MAX_MODEL_LEN} + --max-num-seqs 4 + --gpu-memory-utilization 0.9 + --trust-remote-code + restart: unless-stopped + + open-webui: + image: ghcr.io/open-webui/open-webui:main + container_name: open-webui-vllm + ports: + - "${WEBUI_PORT}:8080" + environment: + - OPENAI_API_BASE_URL=http://vllm:8000/v1 + - OPENAI_API_KEY=none + - OLLAMA_BASE_URL= + volumes: + - open-webui-vllm-data:/app/backend/data + depends_on: + - vllm + restart: unless-stopped + +volumes: + vllm-models: + open-webui-vllm-data: +EOF + echo " ✓ docker-compose.vllm.yml 생성" +else + echo " ✓ docker-compose.vllm.yml 이미 존재" +fi + +echo "" + +#-------------------------------------------------------------------- +# 3. 서비스 실행 +#-------------------------------------------------------------------- +echo "[3/4] vLLM + Open WebUI 실행..." +echo " (첫 실행 시 Docker 이미지 pull + 모델 다운로드로 시간이 걸립니다)" + +cd "$PROJECT_DIR" +docker compose -f docker-compose.vllm.yml up -d 2>&1 | grep -v "^$" + +echo "" +echo " 서버 준비 대기 중..." +for i in $(seq 1 300); do + if curl -s http://localhost:$PORT/v1/models > /dev/null 2>&1; then + echo "" + echo " ✓ vLLM 서버 준비 완료!" + break + fi + # 컨테이너가 죽었는지 확인 + if ! docker ps -q --filter name=vllm-server | grep -q .; then + echo "" + echo " ❌ vLLM 서버 시작 실패. 로그를 확인하세요:" + echo " docker logs vllm-server" + exit 1 + fi + printf "." + sleep 1 +done + +if ! curl -s http://localhost:$PORT/v1/models > /dev/null 2>&1; then + echo "" + echo " ⚠️ 서버가 아직 준비 중입니다. (모델 다운로드 중일 수 있음)" + echo " 로그 확인: docker logs -f vllm-server" +fi + +echo "" + +#-------------------------------------------------------------------- +# 4. 완료 +#-------------------------------------------------------------------- +echo "[4/4] 셋업 완료!" +echo "" +echo "============================================" +echo " 브라우저에서 http://localhost:${WEBUI_PORT} 접속" +echo " (첫 접속 시 회원가입 → 첫 계정이 admin)" +echo "" +echo " 로그 확인: docker logs -f vllm-server" +echo " 종료: ./stop-vllm.sh" +echo "============================================" diff --git a/setup.sh b/setup.sh new file mode 100755 index 0000000..957b9d7 --- /dev/null +++ b/setup.sh @@ -0,0 +1,129 @@ +#!/bin/bash +set -e + +#==================================================================== +# Qwen3.5 + Open WebUI 통합 셋업 +# 시스템 환경을 자동 감지하여 최적의 방식을 선택합니다. +# +# Apple Silicon + RAM 32GB↑ → MLX (vllm-mlx) +# Apple Silicon + RAM 부족 → Ollama +# NVIDIA GPU → vLLM (Docker) +# 그 외 → Ollama +#==================================================================== + +PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)" + +echo "============================================" +echo " Qwen3.5 + Open WebUI 통합 셋업" +echo " 시스템 환경을 자동 감지합니다..." +echo "============================================" +echo "" + +#-------------------------------------------------------------------- +# 시스템 감지 +#-------------------------------------------------------------------- +OS=$(uname -s) +ARCH=$(uname -m) +HAS_APPLE_SILICON=false +HAS_NVIDIA_GPU=false +RAM_GB=0 + +# Apple Silicon 감지 +if [ "$OS" = "Darwin" ] && [ "$ARCH" = "arm64" ]; then + if sysctl -n machdep.cpu.brand_string 2>/dev/null | grep -q "Apple"; then + HAS_APPLE_SILICON=true + RAM_GB=$(sysctl -n hw.memsize | awk '{printf "%.0f", $1/1024/1024/1024}') + fi +fi + +# Linux RAM 감지 +if [ "$OS" = "Linux" ]; then + RAM_GB=$(free -g | awk '/^Mem:/{print $2}') +fi + +# NVIDIA GPU 감지 +if command -v nvidia-smi &>/dev/null; then + if nvidia-smi &>/dev/null; then + HAS_NVIDIA_GPU=true + fi +fi + +echo " OS: $OS ($ARCH)" +echo " Apple Silicon: $HAS_APPLE_SILICON" +echo " NVIDIA GPU: $HAS_NVIDIA_GPU" +echo " RAM: ${RAM_GB}GB" +echo "" + +#-------------------------------------------------------------------- +# 방식 결정 +#-------------------------------------------------------------------- +SELECTED="" + +if [ "$HAS_NVIDIA_GPU" = true ]; then + SELECTED="vllm" + echo " → NVIDIA GPU 감지 → vLLM 선택" +elif [ "$HAS_APPLE_SILICON" = true ] && [ "$RAM_GB" -ge 32 ]; then + SELECTED="mlx" + echo " → Apple Silicon + RAM ${RAM_GB}GB → MLX (vllm-mlx) 선택" +elif [ "$HAS_APPLE_SILICON" = true ]; then + SELECTED="ollama" + echo " → Apple Silicon + RAM ${RAM_GB}GB (32GB 미만) → Ollama 선택" +else + SELECTED="ollama" + echo " → 범용 환경 → Ollama 선택" +fi + +echo "" + +#-------------------------------------------------------------------- +# 사용자 확인 +#-------------------------------------------------------------------- +read -p " $SELECTED 방식으로 진행할까요? [Y/n] (직접 선택: o=ollama, m=mlx, v=vllm): " CONFIRM +CONFIRM=${CONFIRM:-Y} + +case "$CONFIRM" in + [Yy]|"") + # 자동 선택 유지 + ;; + [Oo]) + SELECTED="ollama" + echo " → Ollama로 변경" + ;; + [Mm]) + SELECTED="mlx" + echo " → MLX로 변경" + ;; + [Vv]) + SELECTED="vllm" + echo " → vLLM으로 변경" + ;; + [Nn]) + echo " 취소됨." + exit 0 + ;; + *) + echo " 알 수 없는 입력. 취소됨." + exit 1 + ;; +esac + +echo "" +echo "============================================" +echo " $SELECTED 방식으로 셋업을 시작합니다" +echo "============================================" +echo "" + +#-------------------------------------------------------------------- +# 선택된 스크립트 실행 +#-------------------------------------------------------------------- +case "$SELECTED" in + ollama) + exec "$PROJECT_DIR/setup-ollama.sh" + ;; + mlx) + exec "$PROJECT_DIR/setup-mlx.sh" + ;; + vllm) + exec "$PROJECT_DIR/setup-vllm.sh" + ;; +esac diff --git a/stop-vllm.sh b/stop-vllm.sh new file mode 100755 index 0000000..835dc59 --- /dev/null +++ b/stop-vllm.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +#==================================================================== +# vLLM + Open WebUI 종료 +#==================================================================== + +PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)" + +echo "============================================" +echo " vLLM + Open WebUI 종료" +echo "============================================" +echo "" + +cd "$PROJECT_DIR" + +if [ -f "$PROJECT_DIR/docker-compose.vllm.yml" ]; then + docker compose -f docker-compose.vllm.yml down 2>&1 | grep -v "^$" + echo " ✓ vLLM + Open WebUI 종료" +else + echo " - docker-compose.vllm.yml 없음" +fi + +echo "" +echo " 완료. 재시작하려면: ./setup-vllm.sh" +echo "" diff --git a/stop.sh b/stop.sh new file mode 100755 index 0000000..43c020d --- /dev/null +++ b/stop.sh @@ -0,0 +1,75 @@ +#!/bin/bash + +#==================================================================== +# 통합 종료 — 실행 중인 서비스를 자동 감지하여 종료 +#==================================================================== + +PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)" +STOPPED=false + +echo "============================================" +echo " Open WebUI 서비스 종료" +echo "============================================" +echo "" + +# vLLM 컨테이너 확인 +if docker ps -q --filter name=vllm-server 2>/dev/null | grep -q .; then + echo " [vLLM] 감지됨 → 종료 중..." + cd "$PROJECT_DIR" + docker compose -f docker-compose.vllm.yml down 2>&1 | grep -v "^$" + echo " ✓ vLLM + Open WebUI 종료" + STOPPED=true +fi + +# MLX Open WebUI 컨테이너 확인 +if docker ps -q --filter name=open-webui-mlx 2>/dev/null | grep -q .; then + echo " [MLX] Open WebUI 감지됨 → 종료 중..." + cd "$PROJECT_DIR" + docker compose -f docker-compose.mlx.yml down 2>&1 | grep -v "^$" + echo " ✓ Open WebUI (MLX) 종료" + STOPPED=true +fi + +# MLX vllm-mlx 서버 확인 +PIDS=$(lsof -ti :8090 2>/dev/null || true) +if [ -n "$PIDS" ]; then + echo " [MLX] vllm-mlx 서버 감지됨 → 종료 중..." + echo "$PIDS" | xargs kill -9 2>/dev/null || true + echo " ✓ vllm-mlx 서버 종료" + STOPPED=true +fi + +# MLX 로그 파일 정리 +if [ -f "$PROJECT_DIR/vllm-mlx.log" ]; then + rm "$PROJECT_DIR/vllm-mlx.log" + echo " ✓ MLX 로그 파일 정리" +fi + +# Ollama Open WebUI 컨테이너 확인 +if docker ps -q --filter name=open-webui 2>/dev/null | grep -q .; then + # open-webui-mlx나 open-webui-vllm이 아닌 경우만 + if ! docker ps -q --filter name=open-webui-mlx 2>/dev/null | grep -q . && \ + ! docker ps -q --filter name=open-webui-vllm 2>/dev/null | grep -q .; then + echo " [Ollama] Open WebUI 감지됨 → 종료 중..." + cd "$PROJECT_DIR" + docker compose down 2>&1 | grep -v "^$" + echo " ✓ Open WebUI (Ollama) 종료" + STOPPED=true + fi +fi + +# Ollama 서비스 확인 +if command -v brew &>/dev/null && brew services list 2>/dev/null | grep ollama | grep -q started; then + echo " [Ollama] 서비스 감지됨 → 종료 중..." + brew services stop ollama + echo " ✓ Ollama 서비스 종료" + STOPPED=true +fi + +if [ "$STOPPED" = false ]; then + echo " 실행 중인 서비스가 없습니다." +fi + +echo "" +echo " 완료. 재시작하려면: ./setup.sh" +echo ""