vLLM 지원 추가 및 통합 셋업/종료 스크립트

- 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) <noreply@anthropic.com>
This commit is contained in:
MyeonghoeLee 2026-03-26 15:07:21 +09:00
parent bcd17b2951
commit f1890713a7
5 changed files with 457 additions and 45 deletions

107
README.md
View File

@ -1,53 +1,19 @@
# Qwen3.5 로컬 서빙 + Open WebUI # 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 ```bash
# 셋업 (Ollama 설치 → 모델 다운로드 → Open WebUI 실행) # 시스템 환경을 자동 감지하여 최적의 방식을 선택합니다
./setup-ollama.sh ./setup.sh
# 종료 # 종료 (실행 중인 서비스를 자동 감지하여 종료)
./stop-ollama.sh ./stop.sh
```
### MLX 방식
```bash
# 셋업 (venv → vllm-mlx 설치 → 패치 → Open WebUI → 서버 시작)
./setup-mlx.sh
# 종료
./stop-mlx.sh
``` ```
셋업 완료 후 **http://localhost:3000** 접속 셋업 완료 후 **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.sh # 통합 셋업 (자동 감지)
├── setup-mlx.sh # MLX 원클릭 셋업 ├── stop.sh # 통합 종료 (자동 감지)
├── setup-ollama.sh # Ollama 셋업
├── setup-mlx.sh # MLX 셋업
├── setup-vllm.sh # vLLM 셋업
├── stop-ollama.sh # Ollama 종료 ├── stop-ollama.sh # Ollama 종료
├── stop-mlx.sh # MLX 종료 ├── stop-mlx.sh # MLX 종료
├── docker-compose.yml # Ollama용 Open WebUI ├── stop-vllm.sh # vLLM 종료
├── docker-compose.mlx.yml # MLX용 Open WebUI
├── docker-compose.yml # Ollama용
├── docker-compose.mlx.yml # MLX용
├── docker-compose.vllm.yml # vLLM용 (자동 생성)
├── SETUP_OLLAMA.md # Ollama 상세 가이드 ├── SETUP_OLLAMA.md # Ollama 상세 가이드
└── SETUP_MLX.md # MLX 상세 가이드 (파라미터, 트러블슈팅) └── SETUP_MLX.md # MLX 상세 가이드
``` ```
--- ---

166
setup-vllm.sh Executable file
View File

@ -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 "============================================"

129
setup.sh Executable file
View File

@ -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

25
stop-vllm.sh Executable file
View File

@ -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 ""

75
stop.sh Executable file
View File

@ -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 ""