Skip to content

Instantly share code, notes, and snippets.

@erewok
Last active May 1, 2026 14:35
Show Gist options
  • Select an option

  • Save erewok/39bb6325aa944af36583021f13ed71ab to your computer and use it in GitHub Desktop.

Select an option

Save erewok/39bb6325aa944af36583021f13ed71ab to your computer and use it in GitHub Desktop.
Bootstrapping script for MBP and llama-server with HF model qwen3.6 and pi-code
#!/bin/bash
set -euo pipefail
# Bootstrap script for local LLM coding agent on Apple Silicon Macs (M1-M5)
# Installs: llama.cpp (llama-server), pi-coding-agent
# Model and params auto-tuned based on available unified memory
LLAMA_PORT=3333
LLAMA_HOST=127.0.0.1
# --- Colors ---
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
BOLD='\033[1m'
DIM='\033[2m'
NC='\033[0m'
info() { echo -e "${GREEN}[+]${NC} $*"; }
warn() { echo -e "${YELLOW}[!]${NC} $*"; }
error() { echo -e "${RED}[x]${NC} $*"; exit 1; }
# --- Preflight checks ---
if [[ "$(uname -s)" != "Darwin" ]]; then
error "This script is for macOS only."
fi
ARCH="$(uname -m)"
if [[ "$ARCH" != "arm64" ]]; then
error "Apple Silicon (arm64) required. Detected: $ARCH"
fi
RAM_GB=$(( $(sysctl -n hw.memsize) / 1073741824 ))
info "Detected ${RAM_GB}GB unified memory"
# --- Memory selection ---
# Each tier picks a model that fits comfortably, leaving headroom for OS + KV cache.
#
# 16 GB — Qwen3 8B dense, Q8_0 (8.7 GB model, ~3 GB free for KV + OS)
# 24 GB — Qwen3 8B dense, BF16 (16.4 GB model, ~3.5 GB free) — full precision small model
# 32 GB — Qwen 3.6 35B MoE, Q4_K_M (22.1 GB model, ~5.5 GB free)
# 64 GB — Qwen 3.6 35B MoE, Q8_0 (36.9 GB model, ~23 GB free)
# 128 GB — Qwen 3.6 35B MoE, BF16 (69.4 GB model, ~54 GB free)
#
# KV cache quantization is tuned per tier:
# tight memory → q4_0 (75% savings vs f16)
# moderate → q8_0 (50% savings vs f16)
# plenty → f16 (no quantization)
#
# Context sizes are aggressive — set to maximize the KV cache for the
# pi coding agent harness, which uses these values to determine compaction
# thresholds. Assumes ~3 GB for macOS + 1 GB headroom.
echo ""
echo -e "${BOLD}How much unified memory does this Mac have?${NC}"
echo ""
echo -e " ${CYAN}1)${NC} 16 GB → Qwen3 8B Q8_0 ${DIM}(8.7 GB, dense, fast)${NC}"
echo -e " ${CYAN}2)${NC} 24 GB → Qwen3 8B BF16 ${DIM}(16.4 GB, dense, full precision)${NC}"
echo -e " ${CYAN}3)${NC} 32 GB → Qwen 3.6 35B-A3B Q4_K_M ${DIM}(22.1 GB, MoE, good balance)${NC}"
echo -e " ${CYAN}4)${NC} 64 GB → Qwen 3.6 35B-A3B Q8_0 ${DIM}(36.9 GB, MoE, near-lossless)${NC}"
echo -e " ${CYAN}5)${NC} 128 GB → Qwen 3.6 35B-A3B BF16 ${DIM}(69.4 GB, MoE, full precision)${NC}"
echo ""
read -rp "Select [1-5]: " mem_choice
case "$mem_choice" in
1)
MODEL_HF="unsloth/Qwen3-8B-GGUF:Q8_0"
MODEL_DISPLAY="Qwen3 8B Q8_0 (Local)"
QUANT_LABEL="Q8_0 (8-bit, 8.7 GB dense model)"
MODEL_SIZE_GB=9
CTX_SIZE=16384
CACHE_TYPE_K="q4_0"
CACHE_TYPE_V="q4_0"
THREADS=8
BATCH_SIZE=1024
UBATCH_SIZE=256
REASONING=false
;;
2)
MODEL_HF="unsloth/Qwen3-8B-GGUF:BF16"
MODEL_DISPLAY="Qwen3 8B BF16 (Local)"
QUANT_LABEL="BF16 (16-bit, 16.4 GB dense model)"
MODEL_SIZE_GB=17
CTX_SIZE=16384
CACHE_TYPE_K="q8_0"
CACHE_TYPE_V="q8_0"
THREADS=8
BATCH_SIZE=1024
UBATCH_SIZE=256
REASONING=false
;;
3)
MODEL_HF="unsloth/Qwen3.6-35B-A3B-GGUF:UD-Q4_K_M"
MODEL_DISPLAY="Qwen 3.6 35B Q4_K_M (Local)"
QUANT_LABEL="Q4_K_M (4-bit, 22.1 GB MoE model)"
MODEL_SIZE_GB=22
CTX_SIZE=65536
CACHE_TYPE_K="q4_0"
CACHE_TYPE_V="q4_0"
THREADS=10
BATCH_SIZE=2048
UBATCH_SIZE=512
REASONING=true
;;
4)
MODEL_HF="unsloth/Qwen3.6-35B-A3B-GGUF:Q8_0"
MODEL_DISPLAY="Qwen 3.6 35B Q8_0 (Local)"
QUANT_LABEL="Q8_0 (8-bit, 36.9 GB MoE model)"
MODEL_SIZE_GB=37
CTX_SIZE=262144
CACHE_TYPE_K="q4_0"
CACHE_TYPE_V="q4_0"
THREADS=12
BATCH_SIZE=2048
UBATCH_SIZE=512
REASONING=true
;;
5)
MODEL_HF="unsloth/Qwen3.6-35B-A3B-GGUF:BF16"
MODEL_DISPLAY="Qwen 3.6 35B BF16 (Local)"
QUANT_LABEL="BF16 (16-bit, 69.4 GB MoE model)"
MODEL_SIZE_GB=70
CTX_SIZE=262144
CACHE_TYPE_K="q8_0"
CACHE_TYPE_V="q8_0"
THREADS=12
BATCH_SIZE=2048
UBATCH_SIZE=512
REASONING=true
;;
*)
error "Invalid selection. Please choose 1-5."
;;
esac
echo ""
info "Selected: ${QUANT_LABEL}"
info "Model: ${MODEL_HF}"
info "Context: ${CTX_SIZE} tokens"
info "KV cache: K=${CACHE_TYPE_K}, V=${CACHE_TYPE_V}"
if (( MODEL_SIZE_GB >= RAM_GB )); then
warn "Model size (${MODEL_SIZE_GB}GB) meets or exceeds your RAM (${RAM_GB}GB)."
warn "The system needs memory for the OS, KV cache, and other apps."
warn "This will likely be very slow due to swap. Consider a smaller option."
echo ""
read -rp "Continue anyway? [y/N] " ans
[[ "$ans" =~ ^[Yy]$ ]] || exit 0
fi
# --- Install Homebrew if missing ---
if ! command -v brew &>/dev/null; then
info "Installing Homebrew..."
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
eval "$(/opt/homebrew/bin/brew shellenv)"
fi
# --- Install llama.cpp ---
if ! command -v llama-server &>/dev/null; then
info "Installing llama.cpp via Homebrew..."
brew install llama.cpp
else
info "llama-server already installed: $(which llama-server)"
fi
# --- Install Node.js if missing ---
if ! command -v node &>/dev/null; then
info "Installing Node.js via Homebrew..."
brew install node
else
info "Node.js already installed: $(node --version)"
fi
# --- Install pi-coding-agent ---
if ! npm list -g @mariozechner/pi-coding-agent &>/dev/null; then
info "Installing pi-coding-agent..."
npm install -g @mariozechner/pi-coding-agent
else
info "pi-coding-agent already installed"
fi
# --- Install pi-subagents ---
pi install npm:@tintinweb/pi-subagents
# --- Configure pi to use local llama-server ---
PI_CONFIG_DIR="$HOME/.pi/agent"
PI_MODELS="$PI_CONFIG_DIR/models.json"
mkdir -p "$PI_CONFIG_DIR"
if [[ -f "$PI_MODELS" ]]; then
warn "Pi models config already exists at $PI_MODELS — skipping."
warn "To reconfigure, delete it and re-run this script."
else
info "Writing pi models config to $PI_MODELS"
cat > "$PI_MODELS" <<PIEOF
{
"providers": {
"llama-cpp": {
"baseUrl": "http://localhost:${LLAMA_PORT}/v1",
"api": "openai-completions",
"apiKey": "none",
"compat": {
"supportsDeveloperRole": false,
"supportsReasoningEffort": false
},
"models": [
{
"id": "${MODEL_HF}",
"name": "${MODEL_DISPLAY}",
"reasoning": ${REASONING},
"input": ["text"],
"contextWindow": ${CTX_SIZE},
"maxTokens": 4096,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
}
]
}
}
}
PIEOF
fi
# --- Create llama-server launch script ---
LLAMA_DIR="$HOME/llama"
LLAMA_SCRIPT="$LLAMA_DIR/start.sh"
mkdir -p "$LLAMA_DIR/cache" "$LLAMA_DIR/logs"
if [[ ! -f "$LLAMA_SCRIPT" ]]; then
info "Writing llama-server launch script to $LLAMA_SCRIPT"
cat > "$LLAMA_SCRIPT" <<LLAMAEOF
#!/bin/bash
export LLAMA_CACHE="${LLAMA_DIR}/cache"
export LLAMA_SERVER_LOG_FILE="${LLAMA_DIR}/logs/llama-server.log"
exec llama-server \\
-hf ${MODEL_HF} \\
--host ${LLAMA_HOST} \\
--port ${LLAMA_PORT} \\
--jinja \\
--reasoning-format deepseek \\
-ngl 99 \\
-fa auto \\
-c ${CTX_SIZE} \\
-t ${THREADS} \\
-b ${BATCH_SIZE} \\
-ub ${UBATCH_SIZE} \\
-n 4096 \\
--temp 0.0 \\
--cache-type-k ${CACHE_TYPE_K} \\
--cache-type-v ${CACHE_TYPE_V} \\
--slot-save-path "\$LLAMA_CACHE" \\
--log-file "\$LLAMA_SERVER_LOG_FILE"
LLAMAEOF
chmod +x "$LLAMA_SCRIPT"
else
warn "llama-server launch script already exists at $LLAMA_SCRIPT"
fi
echo ""
info "Setup complete!"
echo ""
echo -e " ${BOLD}Model:${NC} ${MODEL_HF}"
echo -e " ${BOLD}Quant:${NC} ${QUANT_LABEL}"
echo -e " ${BOLD}Context:${NC} ${CTX_SIZE} tokens"
echo -e " ${BOLD}KV cache:${NC} K=${CACHE_TYPE_K}, V=${CACHE_TYPE_V}"
echo -e " ${BOLD}Threads:${NC} ${THREADS}"
echo -e " ${BOLD}Batch:${NC} ${BATCH_SIZE} / ${UBATCH_SIZE}"
echo ""
echo " 1. Start llama-server:"
echo " ${LLAMA_SCRIPT}"
echo ""
echo " (First run downloads the model. Subsequent runs use cache.)"
echo ""
echo " 2. In another terminal, start pi in your project directory:"
echo " cd /path/to/your/project"
echo " pi"
echo ""
echo " Select '${MODEL_DISPLAY}' when prompted."
echo ""
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment