Skip to content

Instantly share code, notes, and snippets.

@icedac
Created February 27, 2026 08:17
Show Gist options
  • Select an option

  • Save icedac/c5d8d0d9f1b9b0fdb7e5884582183168 to your computer and use it in GitHub Desktop.

Select an option

Save icedac/c5d8d0d9f1b9b0fdb7e5884582183168 to your computer and use it in GitHub Desktop.
build_qwen3.5_397b-q8.sh
#!/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