diff --git a/README.md b/README.md index 36e66e8..f1e1f3f 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,7 @@ sudo systemctl status local-ai | `invokeai-import-lora.sh` | 85 | Copies a LoRA `.safetensors` file into InvokeAI's Docker model volume. | | `comfyui-import-lora.sh` | 97 | Copies a LoRA into ComfyUI and prints workflow setup instructions. | | `comfyui-install-ipadapter.sh` | 185 | Installs IP-Adapter nodes + models into ComfyUI for reference-image workflows (same face, different settings). | +| `setup-image-models.sh` | 200 | **GPU-aware** image model installer. Detects VRAM, offers appropriate SD/SDXL/Flux models, installs into InvokeAI and/or ComfyUI. | ### Which setup script should I use? @@ -868,17 +869,35 @@ This is where InvokeAI shines for your use case — take an image and riff on it - **Art style:** "same person, oil painting, renaissance style, dramatic chiaroscuro" 6. Click **Invoke** — iterate by adjusting strength and prompt -### Step 5: Use the Unified Canvas for painting/inpainting +### Step 5: Use the Unified Canvas for inpainting -For more control (paint over specific areas, extend an image): +This is the "fix this specific thing" workflow — brush over a hand, arm, face, background, whatever, and regenerate just that area while keeping everything else untouched. 1. Switch to the **Unified Canvas** tab 2. Upload or paste your image -3. Use the **brush tool** to mask areas you want to change -4. Write a prompt for just the masked area -5. Invoke — only the masked area regenerates +3. Select the **Mask** brush tool (not the paint brush) +4. Brush over **only the area you want to change** — everything else stays locked +5. Write a prompt describing what the masked area should become +6. Set **Denoising Strength** to 0.6–0.8 (higher = more change) +7. Click **Invoke** — only the masked pixels regenerate -Example: mask just the background → prompt "tropical beach sunset" → keeps the face, replaces the background. +**Common inpainting fixes:** + +| Problem | Mask | Prompt | +|---------|------|--------| +| Hand in wrong position | Brush over the arm/hand | "natural hand resting at side, relaxed pose" | +| Extra fingers | Brush over the hand | "normal human hand, five fingers, anatomically correct" | +| Weird eyes | Brush over both eyes | "natural eyes, looking at camera, detailed iris" | +| Bad background | Brush over background only | "clean studio backdrop" or "forest trail, golden hour" | +| Wrong clothing | Brush over the clothing area | "wearing blue denim jacket, casual style" | +| Face swap / aging | Brush over the face | "same person, elderly, wrinkles" or "same person as child" | + +**Tips for better inpainting results:** +- **Mask slightly larger** than the problem area — gives the model room to blend edges +- **Use soft brush edges** (lower brush hardness) for more natural blending +- If the result has visible seams, increase your mask area and try again +- **Lower denoising (0.4–0.5)** for subtle fixes, **higher (0.7–0.9)** for major changes +- Keep your LoRA active during inpainting — it maintains the trained style/face consistency ### Troubleshooting @@ -934,3 +953,30 @@ Ollama will **always try to run** any model — it silently offloads layers to C - `nvidia-smi` shows VRAM maxed out - CPU usage spikes during generation - First token takes much longer than usual + +### Image Generation Model Tiers + +The `setup-image-models.sh` script detects your GPU and offers appropriate models: + +| VRAM | Available Models | Default | Notes | +|------|-----------------|---------|-------| +| ≥ 24GB | SD 1.5, SDXL, SDXL Turbo, Flux.1-schnell, Flux.1-dev | SDXL | All models, no constraints | +| 12–23GB | SD 1.5, SDXL, SDXL Turbo, Flux.1-schnell | SDXL | Flux-dev too tight | +| 8–11GB | SD 1.5, SDXL (tight), SDXL Turbo | SD 1.5 | SDXL works at 512px, may be slow | +| 4–7GB | SD 1.5 (float16) | SD 1.5 | Only SD 1.5 fits | +| < 4GB | none | — | CPU generation not recommended | + +**GPU sharing:** Ollama and image generation share the GPU. Ollama auto-unloads models +after its `KEEP_ALIVE` timeout (default 24h), so image gen gets full VRAM when the LLM +is idle. For immediate unload: `docker exec ollama ollama stop ` + +**Multi-GPU scaling:** With dual GPUs (e.g., 2× RTX 5000 = 32GB total), the VRAM +is summed for tier selection. Both InvokeAI and ComfyUI will use all available GPUs. + +```bash +# Install image models (auto-detects GPU): +./setup-image-models.sh + +# Or auto-install the recommended default: +./setup-image-models.sh --auto +``` diff --git a/laptop_full_setup.sh b/laptop_full_setup.sh index 009f767..b208ee8 100755 --- a/laptop_full_setup.sh +++ b/laptop_full_setup.sh @@ -80,6 +80,31 @@ else CTX=4096; OLLAMA_KV_CACHE="q4_0"; GPU_TIER="CPU only — 4B models (slow)" fi +# ── Image generation model tiers (VRAM-aware) ──────────────────────────────── +# Image gen shares GPU with Ollama — Ollama unloads after KEEP_ALIVE timeout, +# so image gen gets full VRAM when Ollama is idle. +if [[ "$TOTAL_VRAM" -ge 24 ]]; then + IMG_MODELS="SD 1.5, SDXL, SDXL Turbo, Flux.1-dev, Flux.1-schnell" + IMG_TIER="all models including Flux" + IMG_DEFAULT="SDXL" +elif [[ "$TOTAL_VRAM" -ge 12 ]]; then + IMG_MODELS="SD 1.5, SDXL, SDXL Turbo, Flux.1-schnell (tight)" + IMG_TIER="SDXL + Flux-schnell" + IMG_DEFAULT="SDXL" +elif [[ "$TOTAL_VRAM" -ge 8 ]]; then + IMG_MODELS="SD 1.5, SDXL (tight at 512px), SDXL Turbo" + IMG_TIER="SD 1.5 comfortable, SDXL possible" + IMG_DEFAULT="SD 1.5" +elif [[ "$TOTAL_VRAM" -ge 4 ]]; then + IMG_MODELS="SD 1.5 (float16)" + IMG_TIER="SD 1.5 only" + IMG_DEFAULT="SD 1.5" +else + IMG_MODELS="none (CPU generation extremely slow)" + IMG_TIER="CPU only — not recommended" + IMG_DEFAULT="" +fi + # ── new vs update ───────────────────────────────────────────────────────────── IS_UPDATE=false [[ -f "$BASE/docker-compose.yml" ]] && IS_UPDATE=true @@ -95,6 +120,7 @@ echo "" info "Machine : $(hostname)" info "LAN IP : $LOCAL_IP" info "GPU : ${GPU_NAME} (${VRAM_GB}GB VRAM)" +info "Image : $IMG_TIER" $IS_UPDATE && warn "Existing install found. Config files kept unless --force is passed." # ── Q1: Top-level — what to run ─────────────────────────────────────────────── @@ -1596,23 +1622,31 @@ if $INSTALL_AI; then echo " → Auto-summarizes old messages when context fills up (like Claude)" echo " Auto Memory: Install from Admin → Functions → Discover → search 'Auto Memory'" echo " → Automatically stores relevant info as persistent memories across chats" + ($SVC_COMFYUI || $SVC_INVOKEAI) && { + echo "" + echo -e " ${YELLOW}Image Generation — GPU: ${TOTAL_VRAM}GB → $IMG_TIER${NC}" + echo " Install base models (detects your GPU automatically):" + echo " ./setup-image-models.sh # interactive" + echo " ./setup-image-models.sh --auto # install recommended default" + echo " Supports: $IMG_MODELS" + } $SVC_COMFYUI && { echo "" - echo -e " ${YELLOW}Image Generation (ComfyUI → Open WebUI):${NC}" - echo " ComfyUI is pre-configured. To complete setup:" - echo " 1. Open ComfyUI at http://$LOCAL_IP:8188 and install a model (e.g. SD 1.5, SDXL)" + echo -e " ${YELLOW}ComfyUI → Open WebUI (chat-integrated image gen):${NC}" + echo " 1. Run ./setup-image-models.sh to install a base model" echo " 2. In ComfyUI: Settings (gear) → enable 'Dev Mode' → Save workflow as 'API Format'" echo " 3. In Open WebUI: Admin → Settings → Images" echo " Engine: ComfyUI | URL: http://comfyui:8188 (already set via env vars)" echo " 4. Import your workflow JSON and map the prompt/output nodes" echo " 5. Ask any model to 'generate an image of...' — it will use ComfyUI" } - $SVC_INVOKEAI && ! $SVC_COMFYUI && { + $SVC_INVOKEAI && { echo "" - echo -e " ${YELLOW}Image Generation (InvokeAI — standalone):${NC}" - echo " InvokeAI runs at http://$LOCAL_IP:9090 with its own UI" - echo " Note: InvokeAI does NOT integrate with Open WebUI natively" - echo " For Open WebUI integration, enable ComfyUI in the setup wizard" + echo -e " ${YELLOW}InvokeAI (standalone UI — inpainting, img2img, LoRA):${NC}" + echo " InvokeAI runs at http://$LOCAL_IP:9090" + echo " For inpainting: Unified Canvas tab → brush over area → describe replacement" + echo " Import LoRAs: ./invokeai-import-lora.sh " + $SVC_COMFYUI || echo " For Open WebUI chat integration, enable ComfyUI in the setup wizard" } fi if $SVC_KIWIX && [[ "$ZIM_CHOICE" == "3" ]]; then diff --git a/local-ai-setup.sh b/local-ai-setup.sh index 675952c..1c74674 100755 --- a/local-ai-setup.sh +++ b/local-ai-setup.sh @@ -54,10 +54,37 @@ else fi EMBED_MODEL="nomic-embed-text" +# ── Image generation model tiers (VRAM-aware) ──────────────────────────────── +# These vars are used by setup-image-models.sh and printed in status output. +# Image gen shares GPU with Ollama — Ollama unloads after KEEP_ALIVE timeout, +# so image gen gets full VRAM when Ollama is idle. +if [[ "$TOTAL_VRAM" -ge 24 ]]; then + IMG_MODELS="SD 1.5, SDXL, SDXL Turbo, Flux.1-dev, Flux.1-schnell" + IMG_TIER="all models including Flux" + IMG_DEFAULT="SDXL" +elif [[ "$TOTAL_VRAM" -ge 12 ]]; then + IMG_MODELS="SD 1.5, SDXL, SDXL Turbo, Flux.1-schnell (tight)" + IMG_TIER="SDXL + Flux-schnell" + IMG_DEFAULT="SDXL" +elif [[ "$TOTAL_VRAM" -ge 8 ]]; then + IMG_MODELS="SD 1.5, SDXL (tight at 512px), SDXL Turbo" + IMG_TIER="SD 1.5 comfortable, SDXL possible" + IMG_DEFAULT="SD 1.5" +elif [[ "$TOTAL_VRAM" -ge 4 ]]; then + IMG_MODELS="SD 1.5 (float16)" + IMG_TIER="SD 1.5 only" + IMG_DEFAULT="SD 1.5" +else + IMG_MODELS="none (CPU generation extremely slow)" + IMG_TIER="CPU only — not recommended" + IMG_DEFAULT="" +fi + section "Local AI Stack — $($IS_UPDATE && echo UPDATE || echo NEW INSTALL)" info "Base : $BASE" info "IP : $LOCAL_IP" info "GPU : ${VRAM_GB}GB VRAM → $TIER" +info "Image : $IMG_TIER ($IMG_MODELS)" write_if_new() { @@ -879,6 +906,7 @@ fi echo "" echo -e "${GREEN}${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo -e "${GREEN}${BOLD} Done! GPU: ${VRAM_GB}GB → $TIER${NC}" +echo -e "${GREEN}${BOLD} Image gen: $IMG_TIER${NC}" echo -e "${GREEN}${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo "" echo -e " ${CYAN}Open WebUI${NC} → http://$LOCAL_IP:3000" diff --git a/setup-image-models.sh b/setup-image-models.sh new file mode 100755 index 0000000..be7ba1b --- /dev/null +++ b/setup-image-models.sh @@ -0,0 +1,275 @@ +#!/usr/bin/env bash +# Detect GPU VRAM and install appropriate Stable Diffusion models for +# InvokeAI and/or ComfyUI. Run anytime — safe to re-run. +# +# Usage: ./setup-image-models.sh [--auto] +# --auto Skip prompts, install the recommended default for your GPU +set -euo pipefail + +RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m' +CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m' +info() { echo -e "${CYAN}[..]${NC} $*"; } +ok() { echo -e "${GREEN}[OK]${NC} $*"; } +warn() { echo -e "${YELLOW}[!!]${NC} $*"; } + +AUTO=false +[[ "${1:-}" == "--auto" ]] && AUTO=true + +# ── Detect GPU ──────────────────────────────────────────────────────────────── +VRAM_GB=$(nvidia-smi --query-gpu=memory.total --format=csv,noheader,nounits 2>/dev/null \ + | head -1 | awk '{printf "%d", $1/1024}' 2>/dev/null || echo "0") +GPU_COUNT=$(nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null | wc -l || echo "0") +GPU_NAME=$(nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null | head -1 || echo "None") +TOTAL_VRAM=$((VRAM_GB * GPU_COUNT)) + +echo "" +echo -e "${BOLD}━━━ Image Generation Model Setup ━━━${NC}" +echo "" +info "GPU : $GPU_NAME" +[[ "$GPU_COUNT" -gt 1 ]] && info "GPU count : $GPU_COUNT" +info "VRAM/card : ${VRAM_GB}GB" +info "Total VRAM: ${TOTAL_VRAM}GB" +echo "" + +# ── Determine which containers are available ────────────────────────────────── +HAS_INVOKEAI=false +HAS_COMFYUI=false +docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q '^invokeai$' && HAS_INVOKEAI=true +docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q '^comfyui$' && HAS_COMFYUI=true + +if ! $HAS_INVOKEAI && ! $HAS_COMFYUI; then + echo -e "${RED}Error:${NC} Neither InvokeAI nor ComfyUI containers found." + echo " Run the setup script first to deploy the AI stack." + exit 1 +fi + +$HAS_INVOKEAI && info "InvokeAI : found" +$HAS_COMFYUI && info "ComfyUI : found" +echo "" + +# ── Build model menu based on VRAM ──────────────────────────────────────────── +# Model VRAM requirements (generation, not just loading): +# SD 1.5 ~4GB 512x512 native +# SDXL ~7GB 1024x1024 native +# SDXL Turbo ~7GB 512x512 (4-step) +# Flux.1-schnell ~12GB fast, high quality +# Flux.1-dev ~20GB best quality, slow + +declare -a MODEL_IDS=() +declare -a MODEL_NAMES=() +declare -a MODEL_VRAM=() +declare -a MODEL_NOTES=() + +add_model() { + MODEL_IDS+=("$1"); MODEL_NAMES+=("$2"); MODEL_VRAM+=("$3"); MODEL_NOTES+=("$4") +} + +# Always offer SD 1.5 if any GPU exists +if [[ "$TOTAL_VRAM" -ge 4 ]]; then + add_model "sd15" "Stable Diffusion 1.5" "4" "512px native, most LoRA compatible, fast" +fi + +if [[ "$TOTAL_VRAM" -ge 8 ]]; then + add_model "sdxl" "Stable Diffusion XL" "7" "1024px native, better quality, more detail" + add_model "sdxl-turbo" "SDXL Turbo" "7" "4-step generation, very fast, good quality" +fi + +if [[ "$TOTAL_VRAM" -ge 12 ]]; then + add_model "flux-schnell" "Flux.1-schnell" "12" "fast Flux variant, excellent quality" +fi + +if [[ "$TOTAL_VRAM" -ge 20 ]]; then + add_model "flux-dev" "Flux.1-dev" "20" "best quality, slower, needs lots of VRAM" +fi + +if [[ ${#MODEL_IDS[@]} -eq 0 ]]; then + warn "No GPU with sufficient VRAM detected (need at least 4GB)." + warn "CPU-only image generation is extremely slow and not recommended." + exit 1 +fi + +# ── Determine default recommendation ───────────────────────────────────────── +if [[ "$TOTAL_VRAM" -ge 20 ]]; then DEFAULT_ID="flux-dev" +elif [[ "$TOTAL_VRAM" -ge 12 ]]; then DEFAULT_ID="sdxl" +elif [[ "$TOTAL_VRAM" -ge 8 ]]; then DEFAULT_ID="sdxl" +elif [[ "$TOTAL_VRAM" -ge 4 ]]; then DEFAULT_ID="sd15" +else DEFAULT_ID="sd15" +fi + +echo -e "${BOLD}Available models for your ${TOTAL_VRAM}GB GPU:${NC}" +echo "" +for i in "${!MODEL_IDS[@]}"; do + DEFAULT_TAG="" + [[ "${MODEL_IDS[$i]}" == "$DEFAULT_ID" ]] && DEFAULT_TAG=" ${GREEN}← recommended${NC}" + printf " ${BOLD}%d)${NC} %-25s ~%sGB VRAM %s%b\n" \ + $((i+1)) "${MODEL_NAMES[$i]}" "${MODEL_VRAM[$i]}" "${MODEL_NOTES[$i]}" "$DEFAULT_TAG" +done +echo "" + +if $AUTO; then + SELECTED="$DEFAULT_ID" + info "Auto mode: installing $SELECTED" +else + echo -e " Enter number(s) separated by spaces, or press Enter for recommended." + echo -e " Example: ${BOLD}1 2${NC} to install both SD 1.5 and SDXL" + echo "" + read -rp " Selection [recommended]: " CHOICE + + if [[ -z "$CHOICE" ]]; then + SELECTED="$DEFAULT_ID" + else + SELECTED="" + for num in $CHOICE; do + idx=$((num - 1)) + if [[ $idx -ge 0 && $idx -lt ${#MODEL_IDS[@]} ]]; then + SELECTED+=" ${MODEL_IDS[$idx]}" + else + warn "Invalid selection: $num (skipping)" + fi + done + SELECTED="${SELECTED# }" + fi +fi + +[[ -z "$SELECTED" ]] && { warn "No models selected."; exit 1; } + +echo "" +info "Will install: $SELECTED" +echo "" + +# ── HuggingFace model identifiers ──────────────────────────────────────────── +declare -A HF_MODELS=( + [sd15]="stabilityai/stable-diffusion-v1-5" + [sdxl]="stabilityai/stable-diffusion-xl-base-1.0" + [sdxl-turbo]="stabilityai/sdxl-turbo" + [flux-schnell]="black-forest-labs/FLUX.1-schnell" + [flux-dev]="black-forest-labs/FLUX.1-dev" +) + +declare -A MODEL_SIZES=( + [sd15]="~4GB" + [sdxl]="~7GB" + [sdxl-turbo]="~7GB" + [flux-schnell]="~12GB" + [flux-dev]="~24GB" +) + +# ── Install into InvokeAI ──────────────────────────────────────────────────── +if $HAS_INVOKEAI; then + echo -e "${BOLD}━━━ Installing into InvokeAI ━━━${NC}" + + # Make sure container is running + if ! docker ps --format '{{.Names}}' | grep -q '^invokeai$'; then + info "Starting InvokeAI container..." + docker start invokeai + sleep 5 + fi + + for model_id in $SELECTED; do + hf_id="${HF_MODELS[$model_id]:-}" + [[ -z "$hf_id" ]] && { warn "Unknown model: $model_id"; continue; } + info "Installing $model_id (${MODEL_SIZES[$model_id]}) → ${hf_id}..." + info " This may take a while depending on your connection." + + if docker exec invokeai invokeai-model-install --add "$hf_id" 2>&1; then + ok "$model_id installed in InvokeAI" + else + warn "$model_id install failed in InvokeAI — try manually via Model Manager at :9090" + fi + echo "" + done +fi + +# ── Install into ComfyUI ───────────────────────────────────────────────────── +if $HAS_COMFYUI; then + echo -e "${BOLD}━━━ Installing into ComfyUI ━━━${NC}" + info "ComfyUI downloads models on first use via its UI." + info "To pre-download, use the ComfyUI Manager at http://localhost:8188" + echo "" + + # Make sure container is running + if ! docker ps --format '{{.Names}}' | grep -q '^comfyui$'; then + info "Starting ComfyUI container..." + docker start comfyui + sleep 5 + fi + + # For ComfyUI, download checkpoints into the models volume + for model_id in $SELECTED; do + hf_id="${HF_MODELS[$model_id]:-}" + [[ -z "$hf_id" ]] && continue + + # Check if model already exists + CKPT_DIR="/opt/ComfyUI/models/checkpoints" + if docker exec comfyui ls "$CKPT_DIR" 2>/dev/null | grep -qi "${model_id//-/_}"; then + ok "$model_id already present in ComfyUI" + continue + fi + + info "Downloading $model_id for ComfyUI (${MODEL_SIZES[$model_id]})..." + info " Downloading from HuggingFace: $hf_id" + + # Use ComfyUI's built-in download mechanism via python + case "$model_id" in + sd15) + docker exec comfyui bash -c \ + "cd /opt/ComfyUI && python -c \" +from huggingface_hub import hf_hub_download +hf_hub_download('$hf_id', 'v1-5-pruned-emaonly.safetensors', local_dir='models/checkpoints') +\" 2>&1" && ok "$model_id downloaded for ComfyUI" \ + || warn "$model_id download failed — install via ComfyUI Manager UI" + ;; + sdxl) + docker exec comfyui bash -c \ + "cd /opt/ComfyUI && python -c \" +from huggingface_hub import hf_hub_download +hf_hub_download('$hf_id', 'sd_xl_base_1.0.safetensors', local_dir='models/checkpoints') +\" 2>&1" && ok "$model_id downloaded for ComfyUI" \ + || warn "$model_id download failed — install via ComfyUI Manager UI" + ;; + *) + info "$model_id: use ComfyUI Manager to install (complex model structure)" + ;; + esac + echo "" + done +fi + +# ── Summary ─────────────────────────────────────────────────────────────────── +echo "" +echo -e "${GREEN}${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" +echo -e "${GREEN}${BOLD} Image model setup complete!${NC}" +echo -e "${GREEN}${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" +echo "" +echo -e " ${BOLD}Your GPU:${NC} $GPU_NAME — ${TOTAL_VRAM}GB VRAM" +echo -e " ${BOLD}Installed:${NC} $SELECTED" +echo "" +$HAS_INVOKEAI && echo -e " ${CYAN}InvokeAI${NC} → http://localhost:9090" +$HAS_COMFYUI && echo -e " ${CYAN}ComfyUI${NC} → http://localhost:8188" +echo "" + +if $HAS_INVOKEAI; then + echo -e " ${YELLOW}InvokeAI quick start:${NC}" + echo " 1. Open Model Manager → verify your model appears" + echo " 2. Go to Text to Image → select the model" + echo " 3. For inpainting: use the Unified Canvas tab" + echo " - Upload image → brush over the area to change" + echo " - Write what you want in that area → Invoke" + echo "" +fi + +if $HAS_COMFYUI; then + echo -e " ${YELLOW}ComfyUI quick start:${NC}" + echo " 1. Open ComfyUI → load a basic txt2img workflow" + echo " 2. Select your checkpoint in the Load Checkpoint node" + echo " 3. For Open WebUI integration: enable Dev Mode → export API workflow" + echo "" +fi + +echo -e " ${YELLOW}GPU sharing:${NC} Ollama and image gen share the GPU." +echo " Ollama auto-unloads models after 24h idle (KEEP_ALIVE=24h)." +echo " For immediate unload before heavy image gen:" +echo " docker exec ollama ollama stop " +echo "" +echo -e " ${YELLOW}Import LoRAs:${NC} ./invokeai-import-lora.sh " +echo ""