Spaces:
Paused
Paused
Download installpi.sh from HelloSun/testzerogpu1: direct link, hf CLI and curl.
- Browser
- Download file 20.8 kB
-
https://huggingface.co/spaces/HelloSun/testzerogpu1/resolve/main/installpi.sh
- Command line
-
hf download hf://spaces/HelloSun/testzerogpu1/installpi.sh
-
curl -L -o installpi.sh https://huggingface.co/spaces/HelloSun/testzerogpu1/resolve/main/installpi.sh
20.8 kB
| set -euo pipefail | |
| # ============================================================================== | |
| # installpi.sh - 一键全自动安装 pi + llama.cpp (源码编译) + Qwen2.5 模型 | |
| # 特性:无 sudo、无互动、全自动、可断点续传、安装后自动验证 | |
| # 模型:unsloth/GLM-4.7-Flash-GGUF (GLM-4.7-Flash-Q4_K_M.gguf) | |
| # 启动:taskset -c 0-14 llama-server ... -t 15 --jinja --load-mode mlock -c 65536 (64K上下文) | |
| # ============================================================================== | |
| HOME_DIR="$HOME" | |
| # 脚本一开始就把之前安装的 node / pi / llama 路径放进 PATH,避免每次重复装 node | |
| export PATH="${XDG_DATA_HOME:-$HOME/.local/share}/pi-node/current/bin:$HOME/.local/bin:$HOME/.pi/bin:$PATH" | |
| LLAMA_SRC_DIR="$HOME_DIR/llama.cpp" # 源码目录 | |
| LLAMA_BIN="$LLAMA_SRC_DIR/build/bin" # 编译后二进制目录 | |
| MODEL_DIR="$HOME_DIR/models" | |
| PI_MODELS_JSON="$HOME_DIR/.pi/agent/models.json" | |
| LLAMA_PORT=8080 | |
| REPO="unsloth/GLM-4.7-Flash-GGUF" | |
| MODEL_FILE="GLM-4.7-Flash-Q4_K_M.gguf" | |
| MODEL_ID="glm-4.7-flash-q4_k_m" | |
| PROVIDER_ID="llama-cpp-local" | |
| CONTEXT_SIZE=65536 # 上下文 64K | |
| # 基础输出函数 | |
| info() { echo -e "\033[36m[INFO]\033[0m $*"; } | |
| ok() { echo -e "\033[32m[OK]\033[0m $*"; } | |
| warn() { echo -e "\033[33m[WARN]\033[0m $*" >&2; } | |
| error() { echo -e "\033[31m[ERROR]\033[0m $*" >&2; exit 1; } | |
| # 确保必要目录 | |
| mkdir -p "$LLAMA_SRC_DIR" "$MODEL_DIR" "$(dirname "$PI_MODELS_JSON")" "$HOME_DIR/.local/bin" | |
| # 检查必要工具(新增编译依赖) | |
| for cmd in curl python3 tar git cmake g++; do | |
| command -v "$cmd" >/dev/null 2>&1 || error "缺少必要工具: $cmd,请先安装" | |
| done | |
| # ============================================================================== | |
| # 0. 确保 Node.js >=22.19.0 (pi 依赖) | |
| # ============================================================================== | |
| ensure_node() { | |
| if command -v node >/dev/null 2>&1; then | |
| if node -e 'const [maj,min,patch]=process.versions.node.split(".").map(Number); process.exit(maj>22||(maj===22&&(min>19||(min===19&&patch>=0)))?0:1)' >/dev/null 2>&1; then | |
| ok "Node.js 已满足: $(node --version)" | |
| return 0 | |
| else | |
| warn "Node.js 版本过旧: $(node --version),将自动安装新版..." | |
| fi | |
| else | |
| info "未检测到 Node.js,将自动安装..." | |
| fi | |
| info "自动安装 Node.js 22.x (standalone 模式)..." | |
| NODE_DIST_BASE="https://nodejs.org/dist/latest-v22.x" | |
| NODE_BASE_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/pi-node" | |
| NODE_TMP_DIR="/tmp/pi-node-$$" | |
| mkdir -p "$NODE_TMP_DIR" "$NODE_BASE_DIR" | |
| OS="$(uname -s)" | |
| ARCH="$(uname -m)" | |
| case "$OS" in Linux) PLATFORM="linux" ;; Darwin) PLATFORM="darwin" ;; *) error "不支持的 OS: $OS" ;; esac | |
| case "$ARCH" in x86_64|amd64) NODE_ARCH="x64" ;; aarch64|arm64) NODE_ARCH="arm64" ;; *) error "不支持的架构: $ARCH" ;; esac | |
| info "解析 Node.js 下载链接 ($PLATFORM-$NODE_ARCH)..." | |
| curl -fsSL --retry 3 --retry-delay 5 "$NODE_DIST_BASE/SHASUMS256.txt" -o "$NODE_TMP_DIR/SHASUMS256.txt" || error "下载 SHASUMS 失败" | |
| NODE_FILE=$(awk -v suffix="-$PLATFORM-$NODE_ARCH.tar.xz" 'index($2,"node-v")==1 && substr($2,length($2)-length(suffix)+1)==suffix{print $2; exit}' "$NODE_TMP_DIR/SHASUMS256.txt") | |
| [[ -n "$NODE_FILE" ]] || error "找不到对应平台的 Node.js 二进制" | |
| info "下载 Node.js $NODE_FILE ..." | |
| curl -fsSL --retry 3 --retry-delay 5 "$NODE_DIST_BASE/$NODE_FILE" -o "$NODE_TMP_DIR/$NODE_FILE" || error "Node.js 下载失败" | |
| if command -v sha256sum >/dev/null 2>&1; then | |
| (cd "$NODE_TMP_DIR" && grep -F "$NODE_FILE" SHASUMS256.txt > SHASUMS256.selected && sha256sum -c SHASUMS256.selected) || warn "SHA256 校验失败,继续安装..." | |
| fi | |
| if ! command -v xz >/dev/null 2>&1; then | |
| warn "未找到 xz,将尝试使用 tar 自动解压 (若失败请手动安装 xz-utils)" | |
| fi | |
| NODE_EXTRACT_DIR="$NODE_BASE_DIR/${NODE_FILE%.tar.xz}" | |
| rm -rf "$NODE_EXTRACT_DIR" | |
| tar -xf "$NODE_TMP_DIR/$NODE_FILE" -C "$NODE_BASE_DIR" || error "解压 Node.js 失败" | |
| rm -f "$NODE_BASE_DIR/current" | |
| ln -s "$NODE_EXTRACT_DIR" "$NODE_BASE_DIR/current" | |
| rm -rf "$NODE_TMP_DIR" | |
| export PATH="$NODE_BASE_DIR/current/bin:$PATH" | |
| for rc in "$HOME/.bashrc" "$HOME/.zshrc" "$HOME/.profile"; do | |
| if [[ -f "$rc" ]] || [[ "$rc" == "$HOME/.bashrc" ]]; then | |
| grep -q "pi-node/current/bin" "$rc" 2>/dev/null || echo "export PATH=\"$NODE_BASE_DIR/current/bin:\$PATH\"" >> "$rc" | |
| fi | |
| done | |
| if command -v node >/dev/null 2>&1; then | |
| ok "Node.js 安装完成: $(node --version) at $(command -v node)" | |
| else | |
| error "Node.js 安装后仍无法找到,请检查 PATH" | |
| fi | |
| } | |
| ensure_node | |
| command -v npm >/dev/null 2>&1 || error "npm 仍不可用,Node 安装可能失败" | |
| info "npm 版本: $(npm --version)" | |
| # ============================================================================== | |
| # 1. 编译 llama.cpp (从源码) | |
| # ============================================================================== | |
| info "1) 编译 llama.cpp (源码)..." | |
| if [[ "${LLAMA_FORCE_REBUILD:-0}" == "1" ]]; then | |
| info "LLAMA_FORCE_REBUILD=1,清理旧 build 目录强制重编..." | |
| rm -rf "$LLAMA_SRC_DIR/build" | |
| fi | |
| if [[ ! -x "$LLAMA_BIN/llama-server" ]]; then | |
| info "克隆 llama.cpp 仓库 (深度1)..." | |
| if [[ -d "$LLAMA_SRC_DIR/.git" ]]; then | |
| info "源码目录已存在,更新..." | |
| cd "$LLAMA_SRC_DIR" | |
| git pull || warn "git pull 失败,继续使用现有源码" | |
| else | |
| git clone --depth 1 https://github.com/ggml-org/llama.cpp "$LLAMA_SRC_DIR" | |
| fi | |
| cd "$LLAMA_SRC_DIR" | |
| info "执行 cmake 配置 (纯 CPU,不开 NATIVE/DL,避免 MoE/多变体后端在纯 CPU 上崩)..." | |
| rm -rf build | |
| cmake -B build \ | |
| -DGGML_NATIVE=OFF \ | |
| -DGGML_CPU=ON \ | |
| -DGGML_BACKEND_DL=OFF \ | |
| -DLLAMA_BUILD_TESTS=OFF \ | |
| -DCMAKE_BUILD_TYPE=Release | |
| info "开始编译 (使用 -j16)..." | |
| cmake --build build --config Release -j 16 | |
| # 验证编译产物 | |
| if [[ -x "$LLAMA_BIN/llama-server" ]]; then | |
| ok "llama.cpp 编译完成: $LLAMA_BIN/llama-server" | |
| else | |
| error "编译失败,找不到 llama-server" | |
| fi | |
| else | |
| ok "llama-server 已存在,跳过编译 (如需重编: LLAMA_FORCE_REBUILD=1 ./installpi.sh)" | |
| fi | |
| # 更新 PATH | |
| export PATH="$LLAMA_BIN:$HOME_DIR/.local/bin:$PATH" | |
| export LD_LIBRARY_PATH="$LLAMA_BIN:${LD_LIBRARY_PATH:-}" | |
| for rc in "$HOME/.bashrc" "$HOME/.zshrc"; do | |
| touch "$rc" 2>/dev/null || true | |
| grep -q "llama.cpp/build/bin" "$rc" 2>/dev/null || echo "export PATH=\"$LLAMA_BIN:\$PATH\"" >> "$rc" | |
| grep -q "LD_LIBRARY_PATH.*llama.cpp" "$rc" 2>/dev/null || echo "export LD_LIBRARY_PATH=\"$LLAMA_BIN:\$LD_LIBRARY_PATH\"" >> "$rc" | |
| done | |
| # ============================================================================== | |
| # 2. 下载模型 (直接使用指定 URL) | |
| # ============================================================================== | |
| info "2) 下载模型 ${REPO} -> ${MODEL_FILE} ..." | |
| MODEL_URL="https://huggingface.co/${REPO}/resolve/main/${MODEL_FILE}" | |
| MODEL_PATH="$MODEL_DIR/${MODEL_FILE}" | |
| if [[ ! -f "$MODEL_PATH" ]]; then | |
| info "开始下载模型 (支持断点续传)..." | |
| curl -L -C - --retry 3 --retry-delay 5 --progress-bar "$MODEL_URL" -o "$MODEL_PATH" || error "模型下载失败" | |
| ok "模型下载完成: $MODEL_PATH" | |
| else | |
| SIZE=$(stat -c%s "$MODEL_PATH" 2>/dev/null || stat -f%z "$MODEL_PATH" 2>/dev/null || echo 0) | |
| if [[ "$SIZE" -lt 1000000000 ]]; then | |
| warn "模型文件过小 (${SIZE} bytes),可能下载不完整,将尝试续传..." | |
| curl -L -C - --retry 3 --retry-delay 5 --progress-bar "$MODEL_URL" -o "$MODEL_PATH" || error "续传失败" | |
| fi | |
| ok "模型已存在,跳过下载: $MODEL_PATH ($SIZE bytes)" | |
| fi | |
| # ============================================================================== | |
| # 3. 启动 llama-server (使用 taskset 和指定参数,上下文 64K) | |
| # ============================================================================== | |
| info "3) 启动 llama-server (port $LLAMA_PORT, taskset 0-14, context $CONTEXT_SIZE)..." | |
| # 清理旧进程 | |
| if [[ -f "$HOME_DIR/llama-server.pid" ]]; then | |
| OLD_PID=$(cat "$HOME_DIR/llama-server.pid" 2>/dev/null || echo "") | |
| if [[ -n "$OLD_PID" ]] && kill -0 "$OLD_PID" 2>/dev/null; then | |
| info "停止旧 llama-server (PID $OLD_PID)..." | |
| kill "$OLD_PID" 2>/dev/null || true | |
| sleep 2 | |
| kill -9 "$OLD_PID" 2>/dev/null || true | |
| fi | |
| fi | |
| pkill -f "llama-server.*${LLAMA_PORT}" 2>/dev/null || true | |
| sleep 1 | |
| # 按实际 CPU 数决定 taskset 范围 / 线程数 (避免机器核数不足时 taskset 报错) | |
| NPROC=$(nproc 2>/dev/null || echo 16) | |
| if [[ "$NPROC" -ge 15 ]]; then | |
| TASKSET_ARGS=(-c 0-14) | |
| NTHREADS=15 | |
| else | |
| TASKSET_ARGS=(-c "0-$((NPROC-1))") | |
| NTHREADS="$NPROC" | |
| fi | |
| if ! taskset "${TASKSET_ARGS[@]}" true 2>/dev/null; then | |
| warn "taskset ${TASKSET_ARGS[*]} 不可用,改用不绑核启动" | |
| TASKSET_ARGS=() | |
| fi | |
| # 构建命令(prlimit 限制内存 32GB;LD_LIBRARY_PATH 已在前面 export,prlimit 会继承) | |
| CMD=( | |
| taskset "${TASKSET_ARGS[@]}" | |
| prlimit --as=34359738368 | |
| "$LLAMA_BIN/llama-server" | |
| -m "$MODEL_PATH" | |
| --alias "$MODEL_ID" | |
| -t "$NTHREADS" | |
| --jinja | |
| --load-mode mlock | |
| -c "$CONTEXT_SIZE" | |
| --host 127.0.0.1 | |
| --port "$LLAMA_PORT" | |
| ) | |
| # taskset 参数为空时去掉 taskset 本体 | |
| if [[ "${#TASKSET_ARGS[@]}" -eq 0 ]]; then | |
| CMD=(prlimit --as=34359738368 "$LLAMA_BIN/llama-server" -m "$MODEL_PATH" --alias "$MODEL_ID" -t "$NTHREADS" --jinja --load-mode mlock -c "$CONTEXT_SIZE" --host 127.0.0.1 --port "$LLAMA_PORT") | |
| fi | |
| info "启动命令: ${CMD[*]}" | |
| touch "$HOME_DIR/llama-server.log" 2>/dev/null || true | |
| nohup "${CMD[@]}" > "$HOME_DIR/llama-server.log" 2>&1 & | |
| echo $! > "$HOME_DIR/llama-server.pid" | |
| info "llama-server PID: $(cat "$HOME_DIR/llama-server.pid")" | |
| # 等待服务器就绪 (最长 5 分钟) | |
| info "等待 llama-server 就绪 (最多 5 分钟)..." | |
| READY=0 | |
| for i in {1..60}; do | |
| if curl -sf "http://127.0.0.1:${LLAMA_PORT}/health" >/dev/null 2>&1; then | |
| ok "llama-server 已就绪 (http://127.0.0.1:${LLAMA_PORT})" | |
| READY=1 | |
| break | |
| fi | |
| if curl -sf "http://127.0.0.1:${LLAMA_PORT}/v1/models" >/dev/null 2>&1; then | |
| ok "llama-server 已就绪 (via /v1/models)" | |
| READY=1 | |
| break | |
| fi | |
| if ! kill -0 $(cat "$HOME_DIR/llama-server.pid" 2>/dev/null || echo 0) 2>/dev/null; then | |
| error "llama-server 启动失败,请查看 ~/llama-server.log (最后 50 行):\n$(tail -n 50 "$HOME_DIR/llama-server.log" 2>/dev/null)" | |
| fi | |
| echo -n "." | |
| sleep 5 | |
| done | |
| echo "" | |
| if [[ "$READY" -ne 1 ]]; then | |
| error "llama-server 在 5 分钟内未就绪,请查看 ~/llama-server.log" | |
| fi | |
| # ============================================================================== | |
| # 4. 安装 pi agent (全自动,无互动) | |
| # ============================================================================== | |
| info "4) 安装 pi agent (全自动)..." | |
| NPM_PREFIX=$(npm prefix -g 2>/dev/null || npm config get prefix 2>/dev/null || echo "$HOME/.local") | |
| info "npm 全局前缀: $NPM_PREFIX" | |
| # 确保 PATH 包含本地 bin 目录 | |
| export PATH="$HOME/.local/bin:$HOME/.pi/bin:$NPM_PREFIX/bin:$PATH" | |
| NEED_INSTALL=1 | |
| if command -v pi >/dev/null 2>&1; then | |
| ok "pi 已安装: $(pi --version 2>&1 | head -n 1)" | |
| NEED_INSTALL=0 | |
| if [[ "${PI_FORCE_REINSTALL:-0}" == "1" ]]; then | |
| info "PI_FORCE_REINSTALL=1,强制重新安装..." | |
| NEED_INSTALL=1 | |
| fi | |
| fi | |
| if [[ "$NEED_INSTALL" -eq 1 ]]; then | |
| info "安装 pi (@earendil-works/pi-coding-agent)..." | |
| PI_PACKAGE="@earendil-works/pi-coding-agent" | |
| # 优先安装到 ~/.local (无需 sudo) | |
| INSTALL_PREFIX="$HOME/.local" | |
| info "安装到 $INSTALL_PREFIX (无需 sudo)" | |
| set +e | |
| npm install -g --ignore-scripts --no-fund --no-audit --progress=false --prefix "$INSTALL_PREFIX" "$PI_PACKAGE" 2>&1 | tee /tmp/pi-install.log | |
| NPM_STATUS=${PIPESTATUS[0]} | |
| set -e | |
| if [[ "$NPM_STATUS" -ne 0 ]]; then | |
| warn "npm 安装 pi 失败 (code $NPM_STATUS),尝试备用方法: curl 官方脚本..." | |
| curl -fsSL https://pi.dev/install.sh | bash -s -- 2>&1 | tee -a /tmp/pi-install.log || true | |
| fi | |
| export PATH="$HOME/.local/bin:$HOME/.pi/bin:$NPM_PREFIX/bin:$PATH" | |
| NODE_BIN_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/pi-node/current/bin" | |
| export PATH="$NODE_BIN_DIR:$PATH" | |
| hash -r 2>/dev/null || true | |
| fi | |
| PI_BIN=$(command -v pi 2>/dev/null || echo "") | |
| if [[ -z "$PI_BIN" ]]; then | |
| for cand in "$HOME/.local/bin/pi" "$HOME/.pi/bin/pi" "$NPM_PREFIX/bin/pi" "/usr/local/bin/pi"; do | |
| if [[ -x "$cand" ]]; then | |
| PI_BIN="$cand" | |
| export PATH="$(dirname "$cand"):$PATH" | |
| break | |
| fi | |
| done | |
| fi | |
| if [[ -z "$PI_BIN" ]] || [[ ! -x "$PI_BIN" ]]; then | |
| error "pi 安装后仍找不到执行档,请查看 /tmp/pi-install.log" | |
| fi | |
| ok "pi 可用: $PI_BIN ($(pi --version 2>&1 | head -n 1))" | |
| for rc in "$HOME/.bashrc" "$HOME/.zshrc" "$HOME/.profile"; do | |
| touch "$rc" 2>/dev/null || true | |
| PI_BIN_DIR="$(dirname "$PI_BIN")" | |
| if [[ "$PI_BIN_DIR" != "" ]]; then | |
| grep -qF "$PI_BIN_DIR" "$rc" 2>/dev/null || echo "export PATH=\"$PI_BIN_DIR:\$PATH\"" >> "$rc" | |
| fi | |
| grep -qF ".local/bin" "$rc" 2>/dev/null || echo "export PATH=\"\$HOME/.local/bin:\$PATH\"" >> "$rc" | |
| done | |
| # ============================================================================== | |
| # 5. 配置 pi 使用本地模型 (上下文调至极限) | |
| # ============================================================================== | |
| info "5) 配置 pi models.json ($PI_MODELS_JSON)..." | |
| mkdir -p "$(dirname "$PI_MODELS_JSON")" | |
| python3 - "$PI_MODELS_JSON" "$LLAMA_PORT" "$MODEL_ID" "$PROVIDER_ID" "$CONTEXT_SIZE" << 'PY' | |
| import json, os, sys | |
| path, port, model_id, provider, ctx_size = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4], int(sys.argv[5]) | |
| os.makedirs(os.path.dirname(path), exist_ok=True) | |
| cfg = {"providers": {}} | |
| if os.path.exists(path): | |
| try: | |
| with open(path) as f: | |
| loaded = json.load(f) | |
| if isinstance(loaded, dict): | |
| cfg = loaded | |
| if "providers" not in cfg: | |
| cfg["providers"] = {} | |
| except Exception as e: | |
| print(f"[WARN] 无法解析旧 models.json,将覆盖: {e}") | |
| cfg["providers"][provider] = { | |
| "baseUrl": f"http://127.0.0.1:{port}/v1", | |
| "api": "openai-completions", | |
| "apiKey": "not-needed", | |
| "compat": {"supportsDeveloperRole": True, "supportsReasoningEffort": False}, | |
| "models": [{ | |
| "id": model_id, | |
| "name": "GLM-4.7-Flash Q4_K_M (Local)", | |
| "reasoning": True, | |
| "input": ["text"], | |
| "contextWindow": ctx_size, | |
| "maxTokens": ctx_size, | |
| "cost": {"input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0} | |
| }] | |
| } | |
| with open(path, "w") as f: | |
| json.dump(cfg, f, indent=2) | |
| print(f"[OK] 已写入 {path}") | |
| print(json.dumps(cfg, indent=2)) | |
| PY | |
| ok "pi 配置完成" | |
| WRAPPER="$HOME/.local/bin/pi-local" | |
| mkdir -p "$(dirname "$WRAPPER")" | |
| cat > "$WRAPPER" << EOS | |
| #!/usr/bin/env bash | |
| exec pi --provider $PROVIDER_ID --model $MODEL_ID "\$@" | |
| EOS | |
| chmod +x "$WRAPPER" | |
| ok "已建立 pi-local wrapper: $WRAPPER" | |
| for rc in "$HOME/.bashrc" "$HOME/.zshrc"; do | |
| touch "$rc" 2>/dev/null || true | |
| grep -q "alias pi-local" "$rc" 2>/dev/null || echo "alias pi-local=\"pi --provider $PROVIDER_ID --model $MODEL_ID\"" >> "$rc" | |
| done | |
| # ============================================================================== | |
| # 6. 安装后验证 | |
| # ============================================================================== | |
| info "6) 安装后验证..." | |
| VERIFICATION_FAILED=0 | |
| # 6.1 检查 llama-server 健康 | |
| info "[验证 1/4] 检查 llama-server 健康状态..." | |
| if curl -sf "http://127.0.0.1:${LLAMA_PORT}/health" >/dev/null 2>&1; then | |
| ok "llama-server 健康检查通过" | |
| elif curl -sf "http://127.0.0.1:${LLAMA_PORT}/v1/models" >/dev/null 2>&1; then | |
| ok "llama-server (via /v1/models) 健康检查通过" | |
| else | |
| warn "llama-server 健康检查失败" | |
| VERIFICATION_FAILED=1 | |
| fi | |
| # 6.2 测试 OpenAI API 推理 | |
| info "[验证 2/4] 测试 OpenAI 兼容 API 推理..." | |
| API_TEST_RESPONSE=$(curl -sf "http://127.0.0.1:${LLAMA_PORT}/v1/chat/completions" \ | |
| -H "Content-Type: application/json" \ | |
| -d '{ | |
| "model": "'$MODEL_ID'", | |
| "messages": [{"role": "user", "content": "用一句话介绍你自己,不要超过20字"}], | |
| "max_tokens": 50, | |
| "temperature": 0.7 | |
| }' 2>&1 || echo "CURL_FAILED") | |
| if echo "$API_TEST_RESPONSE" | grep -q '"choices"' && echo "$API_TEST_RESPONSE" | grep -q '"content"'; then | |
| ok "OpenAI API 推理测试通过" | |
| echo "$API_TEST_RESPONSE" | python3 -m json.tool 2>/dev/null | head -n 30 || echo "$API_TEST_RESPONSE" | head -n 20 | |
| elif echo "$API_TEST_RESPONSE" | grep -q "CURL_FAILED"; then | |
| warn "API 推理 curl 失败: $API_TEST_RESPONSE" | |
| VERIFICATION_FAILED=1 | |
| else | |
| warn "API 推理回应异常,可能是模型仍在加载中..." | |
| echo "$API_TEST_RESPONSE" | head -n 50 >&2 | |
| fi | |
| # 6.3 检查 pi 可用 | |
| info "[验证 3/4] 检查 pi 指令..." | |
| if pi --version >/dev/null 2>&1; then | |
| ok "pi --version: $(pi --version 2>&1 | head -n 1)" | |
| else | |
| warn "pi --version 执行失败" | |
| VERIFICATION_FAILED=1 | |
| fi | |
| if pi --help >/dev/null 2>&1; then | |
| ok "pi --help 正常" | |
| fi | |
| if python3 -c "import json; d=json.load(open('$PI_MODELS_JSON')); assert '$PROVIDER_ID' in d.get('providers',{}); assert any(m['id']=='$MODEL_ID' for m in d['providers']['$PROVIDER_ID'].get('models',[]))" 2>/dev/null; then | |
| ok "pi models.json 配置正确" | |
| else | |
| warn "pi models.json 配置检查失败" | |
| cat "$PI_MODELS_JSON" 2>/dev/null || echo "文件不存在" | |
| VERIFICATION_FAILED=1 | |
| fi | |
| if [[ -x "$WRAPPER" ]]; then | |
| ok "pi-local wrapper 存在且可执行" | |
| else | |
| warn "pi-local wrapper 缺失" | |
| VERIFICATION_FAILED=1 | |
| fi | |
| # 6.4 测试 pi-local 实际可用 | |
| info "[验证 4/4] 测试 pi-local 实际推理 (超时 120 秒)..." | |
| export PATH="$HOME/.local/bin:$HOME/.pi/bin:$(dirname "$PI_BIN"):$PATH" | |
| hash -r 2>/dev/null || true | |
| set +e | |
| PI_TEST_OUTPUT=$(timeout 120 pi --provider "$PROVIDER_ID" --model "$MODEL_ID" -p "用一句话介绍你自己,不超过20字" 2>&1) | |
| PI_EXIT_CODE=$? | |
| set -e | |
| if [[ $PI_EXIT_CODE -eq 0 ]] && [[ -n "$PI_TEST_OUTPUT" ]]; then | |
| ok "pi 推理测试通过 (exit 0)" | |
| echo "--- pi 输出 ---" | |
| echo "$PI_TEST_OUTPUT" | head -n 30 | |
| echo "----------------" | |
| elif [[ $PI_EXIT_CODE -eq 124 ]]; then | |
| warn "pi 测试超时 (120s),可能模型加载较慢或首次启动较久。请稍后手动重试: pi --provider $PROVIDER_ID --model $MODEL_ID -p \"你好\"" | |
| else | |
| warn "pi 测试失败 (exit $PI_EXIT_CODE),输出:" | |
| echo "$PI_TEST_OUTPUT" | head -n 100 >&2 | |
| VERIFICATION_FAILED=1 | |
| fi | |
| # ============================================================================== | |
| # 总结 | |
| # ============================================================================== | |
| echo "" | |
| echo "================================================================" | |
| if [[ "$VERIFICATION_FAILED" -eq 0 ]]; then | |
| echo -e "\033[32m✅ 安装完成且验证通过!\033[0m" | |
| else | |
| echo -e "\033[33m⚠️ 安装完成,但部分验证未通过,请查看上方警告\033[0m" | |
| fi | |
| echo "================================================================" | |
| echo "- 模型: $REPO ($MODEL_FILE)" | |
| echo " 模型文件: $MODEL_PATH" | |
| echo "- 内存限制: 32GB" | |
| echo "- llama-server (编译自源码): $LLAMA_BIN/llama-server" | |
| echo " 启动参数: taskset -c 0-14 ... -t 15 --jinja --load-mode mlock -c $CONTEXT_SIZE --host 127.0.0.1 --port $LLAMA_PORT" | |
| echo "- 服务: http://127.0.0.1:$LLAMA_PORT (log: ~/llama-server.log, pid: ~/llama-server.pid)" | |
| echo "- pi: $PI_BIN ($(pi --version 2>&1 | head -n 1))" | |
| echo "- pi 配置: $PI_MODELS_JSON (provider: $PROVIDER_ID, model: $MODEL_ID, context: $CONTEXT_SIZE)" | |
| echo "- 快速使用:" | |
| echo " source ~/.bashrc # 或 source ~/.zshrc" | |
| echo " pi-local -p \"你好\"" | |
| echo " pi --provider $PROVIDER_ID --model $MODEL_ID -p \"你好\"" | |
| echo "- 停止服务器: kill \$(cat ~/llama-server.pid)" | |
| echo "- 重新验证: curl http://127.0.0.1:$LLAMA_PORT/v1/models && pi-local -p \"测试\"" | |
| echo "================================================================" | |
| if [[ "$VERIFICATION_FAILED" -ne 0 ]]; then | |
| echo "请检查日志:" | |
| echo " - llama-server: ~/llama-server.log (tail -n 100 ~/llama-server.log)" | |
| echo " - pi 安装: /tmp/pi-install.log" | |
| echo " - 模型: $MODEL_DIR/" | |
| exit 1 | |
| fi | |
| exit 0 |