#!/usr/bin/env bash # Janus-35B — fetch the vision projector (mmproj) for image input. # # Why this is separate from build.sh: # build.sh is for the Ollama text path. The mmproj is only known-good on # llama.cpp / llama-cpp-python: attaching it to Ollama was observed failing # with `unknown model architecture` and has not been re-tested since # ollama/ollama#14575 closed (see README Vision section). Note there is no # "vendored llama.cpp fork" -- Ollama serves every GGML model through the # bundled llama-server (ollama/ollama#16031); the old wording here was wrong. # # Usage: # ./scripts/fetch_vision.sh # default: BF16 (902 MB) # MMPROJ_PATH=/path/to/proj.gguf ./scripts/fetch_vision.sh # destination override # # The Heretic GGUF repo publishes the projector as BF16 only (its Files # listing, HF API, 2026-09-18); for another precision, point REPO_ID / # FILE_NAME at a projector from any other Qwen3.6-35B-A3B GGUF repo: # REPO_ID= FILE_NAME=mmproj-F16.gguf ./scripts/fetch_vision.sh # (vision tokens are projected the same way across Qwen 3.6 35B-A3B # finetunes, so a family projector is functionally interchangeable.) # # Requires: huggingface-cli (or hf). set -euo pipefail # Resolve a Hugging Face CLI. The bare name `hf` is ambiguous: Arch's `hf` # package ships an unrelated hidden-file utility under that name, so probing # with `command -v hf` alone can select the wrong binary and turn a download # into a baffling argument error. Prefer the unambiguous `huggingface-cli`, # and accept `hf` only when its help text identifies it as Hugging Face's. detect_hf() { local candidate for candidate in huggingface-cli hf; do command -v "${candidate}" >/dev/null 2>&1 || continue if "${candidate}" --help 2>&1 | grep -qiE 'huggingface|hf\.co'; then printf '%s\n' "${candidate}" return 0 fi done return 1 } PRECISION="${1:-${PRECISION:-BF16}}" REPO_ID="${REPO_ID:-llmfan46/Qwen3.6-35B-A3B-uncensored-heretic-GGUF}" FILE_NAME="${FILE_NAME:-Qwen3.6-35B-A3B-uncensored-heretic-mmproj-${PRECISION}.gguf}" ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" DEST="${MMPROJ_PATH:-${ROOT}/${FILE_NAME}}" echo "[*] repo: ${REPO_ID}" echo "[*] precision: ${PRECISION}" echo "[*] file: ${FILE_NAME}" echo "[*] dest: ${DEST}" if [[ -f "${DEST}" ]]; then echo "[=] already present at ${DEST}, skipping." exit 0 fi if ! HF="$(detect_hf)"; then echo "[!] No Hugging Face CLI found (an unrelated 'hf' on PATH does not count)." >&2 echo " pip install -U huggingface_hub" >&2 exit 1 fi DEST_DIR="$(dirname "${DEST}")" mkdir -p "${DEST_DIR}" "${HF}" download "${REPO_ID}" "${FILE_NAME}" --local-dir "${DEST_DIR}" # `hf download REPO FILE --local-dir DIR` writes to DIR/FILE: only the directory # part of MMPROJ_PATH is honoured and the basename is discarded. Without this # move, MMPROJ_PATH=~/models/mmproj.gguf downloaded ~900 MB successfully and then # reported "download failed" and exited 1, and the re-run guard above probed a # path that was never created. FETCHED="${DEST_DIR}/${FILE_NAME}" if [[ "${FETCHED}" != "${DEST}" && -f "${FETCHED}" ]]; then mv -- "${FETCHED}" "${DEST}" fi if [[ ! -f "${DEST}" ]]; then echo "[!] download failed: ${DEST} not present." >&2 exit 1 fi echo echo "[+] Done. Use it via:" echo " python ${ROOT}/examples/llama_cpp_vision.py \\" echo " --gguf ${ROOT}/Janus-35B-A3B.Q4_K_M.gguf \\" echo " --mmproj ${DEST} \\" echo " --image /path/to/photo.jpg \\" echo " --prompt 'Describe this image.'"