Created
February 27, 2026 08:17
-
-
Save icedac/c5d8d0d9f1b9b0fdb7e5884582183168 to your computer and use it in GitHub Desktop.
build_qwen3.5_397b-q8.sh
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # | |
| # Qwen3.5-397B-A17B Q8_0 — Ollama 원클릭 빌드 스크립트 | |
| # | |
| # 환경: macOS (Apple Silicon, 512GB+ 통합 메모리 권장) | |
| # 용도: unsloth Q8_0 GGUF를 다운로드 → 머지 → Ollama 소스 패치 빌드 → 모델 등록 → 테스트 | |
| # | |
| # 사용법: | |
| # chmod +x build_qwen3.5_397b-q8.sh | |
| # ./build_qwen3.5_397b-q8.sh [WORK_DIR] | |
| # | |
| # WORK_DIR 기본값: /Volumes/External SSD/ollama | |
| # | |
| set -euo pipefail | |
| WORK_DIR="${1:-/Volumes/External SSD/ollama}" | |
| MODEL_NAME="qwen3.5:397b-q8" | |
| HF_REPO="unsloth/Qwen3.5-397B-A17B-GGUF" | |
| HF_PATTERN="Qwen3.5-397B-A17B-Q8_0/*" | |
| SPLIT_COUNT=10 | |
| DOWNLOAD_DIR="$WORK_DIR/download" | |
| MERGED_GGUF="$WORK_DIR/Qwen3.5-397B-A17B-Q8_0.gguf" | |
| OLLAMA_SRC="$WORK_DIR/ollama-src" | |
| OLLAMA_MODELS="$WORK_DIR/models" | |
| MODELFILE="$WORK_DIR/Modelfile.qwen35" | |
| RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m' | |
| info() { echo -e "${GREEN}[INFO]${NC} $*"; } | |
| warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } | |
| fail() { echo -e "${RED}[FAIL]${NC} $*"; exit 1; } | |
| # ───────────────────────────────────────────── | |
| # 1. 도구 설치 | |
| # ───────────────────────────────────────────── | |
| info "1/6 도구 설치 확인" | |
| command -v brew >/dev/null || fail "Homebrew가 필요합니다: https://brew.sh" | |
| for pkg in go cmake llama.cpp; do | |
| brew list "$pkg" &>/dev/null || { info " brew install $pkg"; brew install "$pkg"; } | |
| done | |
| if ! command -v huggingface-cli &>/dev/null; then | |
| info " huggingface-cli 설치 (pipx)" | |
| command -v pipx >/dev/null || brew install pipx | |
| pipx install "huggingface_hub[cli]" | |
| fi | |
| info " go $(go version | awk '{print $3}') / llama-gguf-split $(command -v llama-gguf-split)" | |
| # ───────────────────────────────────────────── | |
| # 2. GGUF 다운로드 | |
| # ───────────────────────────────────────────── | |
| info "2/6 GGUF 다운로드 (~422GB)" | |
| SPLIT_DIR="$DOWNLOAD_DIR/Qwen3.5-397B-A17B-Q8_0" | |
| FIRST_SPLIT="$SPLIT_DIR/Qwen3.5-397B-A17B-Q8_0-00001-of-$(printf '%05d' $SPLIT_COUNT).gguf" | |
| if [ -f "$FIRST_SPLIT" ]; then | |
| info " 이미 다운로드됨, 스킵" | |
| else | |
| mkdir -p "$DOWNLOAD_DIR" | |
| huggingface-cli download "$HF_REPO" \ | |
| --include "$HF_PATTERN" \ | |
| --local-dir "$DOWNLOAD_DIR" | |
| fi | |
| # ───────────────────────────────────────────── | |
| # 3. 분할 파일 머지 | |
| # ───────────────────────────────────────────── | |
| info "3/6 분할 GGUF 머지 → 단일 파일 (~393GB)" | |
| if [ -f "$MERGED_GGUF" ]; then | |
| info " 이미 머지됨, 스킵" | |
| else | |
| llama-gguf-split --merge "$FIRST_SPLIT" "$MERGED_GGUF" | |
| fi | |
| # ───────────────────────────────────────────── | |
| # 4. Ollama 소스 클론 + 패치 + 빌드 | |
| # ───────────────────────────────────────────── | |
| info "4/6 Ollama 소스 빌드 (패치 포함)" | |
| if [ ! -d "$OLLAMA_SRC/.git" ]; then | |
| git clone https://github.com/ollama/ollama.git "$OLLAMA_SRC" | |
| fi | |
| cd "$OLLAMA_SRC" | |
| git checkout main && git pull --ff-only | |
| # ── 패치 적용 ── | |
| DELTANET="model/models/qwen3next/deltanet.go" | |
| MODELGO="model/models/qwen3next/model.go" | |
| [ -f "$DELTANET" ] || fail "$DELTANET 파일 없음 — Ollama 버전 확인 필요" | |
| [ -f "$MODELGO" ] || fail "$MODELGO 파일 없음 — Ollama 버전 확인 필요" | |
| # 패치 1: ssm_dt 텐서 이름 매핑 (ssm_dt → ssm_dt.bias) | |
| if ! grep -q 'ssm_dt\.bias' "$DELTANET"; then | |
| info " 패치 1/3: ssm_dt.bias 텐서 매핑" | |
| sed -i '' 's|gguf:"ssm_dt"|gguf:"ssm_dt.bias,alt:ssm_dt"|' "$DELTANET" | |
| fi | |
| # 패치 2: full_attention_interval 폴백 + vHeadReordered/mropeInterleaved 기본값 수정 | |
| if ! grep -q 'fullAttnInterval' "$MODELGO"; then | |
| info " 패치 2/3: full_attention_interval 폴백 로직" | |
| # head_count_kv 루프 끝 직후에 폴백 로직 삽입 | |
| ANCHOR='if !hasZero || !hasFull {' | |
| PATCH='// [patch] scalar head_count_kv fallback: use full_attention_interval\ | |
| if !hasZero \&\& hasFull {\ | |
| fullAttnInterval := int(c.Uint("full_attention_interval"))\ | |
| if fullAttnInterval > 0 {\ | |
| hasZero = false\ | |
| hasFull = false\ | |
| for i := range numLayers {\ | |
| if (i+1)%fullAttnInterval != 0 {\ | |
| isRecurrent[i] = true\ | |
| hasZero = true\ | |
| } else {\ | |
| hasFull = true\ | |
| }\ | |
| }\ | |
| }\ | |
| }\ | |
| ' | |
| sed -i '' "/$ANCHOR/i\\ | |
| $PATCH | |
| " "$MODELGO" | |
| fi | |
| # 패치 3a: vHeadReordered 기본값 false → true | |
| if grep -q '"ssm.v_head_reordered", false' "$MODELGO"; then | |
| info " 패치 3a/3: vHeadReordered 기본값 → true" | |
| sed -i '' 's/"ssm.v_head_reordered", false/"ssm.v_head_reordered", true/' "$MODELGO" | |
| fi | |
| # 패치 3b: mropeInterleaved 기본값 false → true | |
| if grep -q '"mrope_interleaved", false' "$MODELGO"; then | |
| info " 패치 3b/3: mropeInterleaved 기본값 → true" | |
| sed -i '' 's/"mrope_interleaved", false/"mrope_interleaved", true/' "$MODELGO" | |
| fi | |
| info " 패치 확인:" | |
| git diff --stat HEAD | |
| # 빌드 (go build로 바이너리 생성) | |
| info " go build..." | |
| go build -o "$WORK_DIR/ollama-patched" . | |
| # ───────────────────────────────────────────── | |
| # 5. Modelfile 생성 + 모델 등록 | |
| # ───────────────────────────────────────────── | |
| info "5/6 Modelfile 작성 + 모델 등록" | |
| cat > "$MODELFILE" << EOF | |
| FROM $MERGED_GGUF | |
| RENDERER qwen3.5 | |
| PARSER qwen3.5 | |
| PARAMETER stop "<|im_end|>" | |
| PARAMETER stop "<|endoftext|>" | |
| PARAMETER temperature 0.6 | |
| PARAMETER top_p 0.95 | |
| EOF | |
| info " Modelfile 내용:" | |
| cat "$MODELFILE" | |
| echo | |
| # 서버 시작 (백그라운드) | |
| info " 패치된 Ollama 서버 시작..." | |
| OLLAMA_MODELS="$OLLAMA_MODELS" OLLAMA_NEW_ENGINE=1 \ | |
| "$WORK_DIR/ollama-patched" serve &>/tmp/ollama-build-serve.log & | |
| OLLAMA_PID=$! | |
| trap "kill $OLLAMA_PID 2>/dev/null || true" EXIT | |
| # 서버 준비 대기 | |
| for i in $(seq 1 30); do | |
| if curl -sf http://localhost:11434/api/version &>/dev/null; then break; fi | |
| sleep 1 | |
| done | |
| curl -sf http://localhost:11434/api/version &>/dev/null || fail "Ollama 서버 시작 실패" | |
| # 모델 등록 | |
| info " ollama create $MODEL_NAME" | |
| OLLAMA_MODELS="$OLLAMA_MODELS" "$WORK_DIR/ollama-patched" create "$MODEL_NAME" -f "$MODELFILE" | |
| # ───────────────────────────────────────────── | |
| # 6. 테스트 | |
| # ───────────────────────────────────────────── | |
| info "6/6 테스트" | |
| # 기본 채팅 테스트 | |
| info " [테스트 1] 기본 응답" | |
| RESPONSE=$(curl -sf http://localhost:11434/api/chat -d "{ | |
| \"model\": \"$MODEL_NAME\", | |
| \"messages\": [{\"role\": \"user\", \"content\": \"What is 2+3? Answer with just the number. /no_think\"}], | |
| \"stream\": false | |
| }" | python3 -c "import sys,json; print(json.load(sys.stdin)['message']['content'])" 2>/dev/null) | |
| echo " 응답: $RESPONSE" | |
| if echo "$RESPONSE" | grep -q "5"; then | |
| info " ✓ 기본 응답 정상" | |
| else | |
| warn " ✗ 기본 응답 이상 — 수동 확인 필요" | |
| fi | |
| # Tool calling 테스트 | |
| info " [테스트 2] Tool calling" | |
| TOOL_RESPONSE=$(curl -sf http://localhost:11434/api/chat -d '{ | |
| "model": "'"$MODEL_NAME"'", | |
| "messages": [{"role": "user", "content": "What is 2+3?"}], | |
| "tools": [{"type": "function", "function": {"name": "calc", "description": "Calculate math", "parameters": {"type": "object", "properties": {"expr": {"type": "string"}}, "required": ["expr"]}}}], | |
| "stream": false | |
| }' | python3 -c "import sys,json; r=json.load(sys.stdin)['message']; print('tool_calls' in r and len(r.get('tool_calls',[])) > 0)" 2>/dev/null) | |
| if [ "$TOOL_RESPONSE" = "True" ]; then | |
| info " ✓ Tool calling 정상" | |
| else | |
| warn " ✗ Tool calling 이상 — 수동 확인 필요" | |
| fi | |
| # 서버 종료 | |
| kill $OLLAMA_PID 2>/dev/null || true | |
| trap - EXIT | |
| # ───────────────────────────────────────────── | |
| echo "" | |
| info "═══════════════════════════════════════════" | |
| info " 빌드 완료!" | |
| info "═══════════════════════════════════════════" | |
| info "" | |
| info " 실행 방법:" | |
| info " OLLAMA_MODELS=\"$OLLAMA_MODELS\" OLLAMA_NEW_ENGINE=1 \\" | |
| info " \"$WORK_DIR/ollama-patched\" serve" | |
| info "" | |
| info " 다른 터미널:" | |
| info " OLLAMA_MODELS=\"$OLLAMA_MODELS\" \\" | |
| info " \"$WORK_DIR/ollama-patched\" run $MODEL_NAME" | |
| info "" | |
| info " Claude Code 연동:" | |
| info " claude --model ollama:$MODEL_NAME" | |
| info "" | |
| info " 원본 파일 정리 (선택):" | |
| info " rm -rf \"$DOWNLOAD_DIR\" # ~422GB" | |
| info " rm \"$MERGED_GGUF\" # ~393GB" | |
| info "═══════════════════════════════════════════" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment