|
|
@@ -4,24 +4,29 @@
|
|
|
|
# .ai-config (settings used by build-ai-image.sh / run-ai-image.sh)
|
|
|
|
# .ai-config (settings used by build-ai-image.sh / run-ai-image.sh)
|
|
|
|
# RUN_AIDER.md (how to run aider against the container on this machine)
|
|
|
|
# RUN_AIDER.md (how to run aider against the container on this machine)
|
|
|
|
#
|
|
|
|
#
|
|
|
|
# Usage: ./setup.sh [--model <ollama-tag>] [--gpu|--no-gpu] [--yes]
|
|
|
|
# Usage: ./setup.sh [--model <ollama-tag>] [--gpu|--no-gpu] [--port <n>] [--yes]
|
|
|
|
set -euo pipefail
|
|
|
|
set -euo pipefail
|
|
|
|
cd "$(dirname "$0")"
|
|
|
|
cd "$(dirname "$0")"
|
|
|
|
|
|
|
|
# Numeric formatting/parsing below assumes '.' as the decimal point (awk, sort -n).
|
|
|
|
|
|
|
|
# A locale like de_DE (comma decimal separator) would corrupt those comparisons.
|
|
|
|
|
|
|
|
export LC_ALL=C
|
|
|
|
|
|
|
|
|
|
|
|
FORCE_MODEL=""; FORCE_GPU=""; ASSUME_YES=0
|
|
|
|
FORCE_MODEL=""; FORCE_GPU=""; ASSUME_YES=0; FORCE_PORT=""
|
|
|
|
while [ $# -gt 0 ]; do
|
|
|
|
while [ $# -gt 0 ]; do
|
|
|
|
case "$1" in
|
|
|
|
case "$1" in
|
|
|
|
--model) FORCE_MODEL="${2:?--model needs a value}"; shift ;;
|
|
|
|
--model) FORCE_MODEL="${2:?--model needs a value}"; shift ;;
|
|
|
|
--gpu) FORCE_GPU=1 ;;
|
|
|
|
--gpu) FORCE_GPU=1 ;;
|
|
|
|
--no-gpu) FORCE_GPU=0 ;;
|
|
|
|
--no-gpu) FORCE_GPU=0 ;;
|
|
|
|
|
|
|
|
--port) FORCE_PORT="${2:?--port needs a value}"; shift ;;
|
|
|
|
--yes|-y) ASSUME_YES=1 ;;
|
|
|
|
--yes|-y) ASSUME_YES=1 ;;
|
|
|
|
-h|--help) sed -n '2,8p' "$0" | sed 's/^# \?//'; exit 0 ;;
|
|
|
|
-h|--help) sed -n '2,9p' "$0" | sed 's/^# \?//'; exit 0 ;;
|
|
|
|
*) echo "Unknown option: $1" >&2; exit 1 ;;
|
|
|
|
*) echo "Unknown option: $1" >&2; exit 1 ;;
|
|
|
|
esac
|
|
|
|
esac
|
|
|
|
shift
|
|
|
|
shift
|
|
|
|
done
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
|
|
IMAGE_NAME="local-ai"; CONTAINER_NAME="local-ai"; PORT=11434
|
|
|
|
IMAGE_NAME="local-ai"; CONTAINER_NAME="local-ai"; PORT=""
|
|
|
|
|
|
|
|
port_busy() { (exec 3<>"/dev/tcp/127.0.0.1/$1") 2>/dev/null; }
|
|
|
|
NOTES=() # machine-specific remarks, echoed to the terminal and RUN_AIDER.md
|
|
|
|
NOTES=() # machine-specific remarks, echoed to the terminal and RUN_AIDER.md
|
|
|
|
|
|
|
|
|
|
|
|
# Distro-specific commands to make an NVIDIA GPU usable from Docker.
|
|
|
|
# Distro-specific commands to make an NVIDIA GPU usable from Docker.
|
|
|
@@ -127,9 +132,12 @@ echo
|
|
|
|
|
|
|
|
|
|
|
|
# ------------------------------------------------ memory budget / context
|
|
|
|
# ------------------------------------------------ memory budget / context
|
|
|
|
# What a model must fit into: VRAM when a GPU is used, else RAM minus headroom
|
|
|
|
# What a model must fit into: VRAM when a GPU is used, else RAM minus headroom
|
|
|
|
# for the OS, aider and the editor.
|
|
|
|
# for the OS, aider and the editor. hard_budget/gpu_hard have almost no headroom -
|
|
|
|
|
|
|
|
# a model only fitting there is "tight" (usable, but close other apps first).
|
|
|
|
budget=$(awk -v r="$ram_gb" 'BEGIN {b=r-4; if (b<1) b=1; printf "%.1f", b}')
|
|
|
|
budget=$(awk -v r="$ram_gb" 'BEGIN {b=r-4; if (b<1) b=1; printf "%.1f", b}')
|
|
|
|
|
|
|
|
hard_budget=$(awk -v r="$ram_gb" 'BEGIN {b=r-1; if (b<1) b=1; printf "%.1f", b}')
|
|
|
|
gpu_budget=$(awk -v v="$vram_gb" 'BEGIN {printf "%.1f", v*0.9}')
|
|
|
|
gpu_budget=$(awk -v v="$vram_gb" 'BEGIN {printf "%.1f", v*0.9}')
|
|
|
|
|
|
|
|
gpu_hard=$(awk -v v="$vram_gb" 'BEGIN {printf "%.1f", v*0.98}')
|
|
|
|
|
|
|
|
|
|
|
|
if awk -v b="$budget" 'BEGIN{exit !(b<6)}'; then CONTEXT=4096
|
|
|
|
if awk -v b="$budget" 'BEGIN{exit !(b<6)}'; then CONTEXT=4096
|
|
|
|
elif awk -v b="$budget" 'BEGIN{exit !(b>=40)}'; then CONTEXT=32768
|
|
|
|
elif awk -v b="$budget" 'BEGIN{exit !(b>=40)}'; then CONTEXT=32768
|
|
|
@@ -143,10 +151,14 @@ for i in "${!rows[@]}"; do
|
|
|
|
IFS=$'\t' read -r tag disk mem cpu_ok rank desc <<<"${rows[$i]}"
|
|
|
|
IFS=$'\t' read -r tag disk mem cpu_ok rank desc <<<"${rows[$i]}"
|
|
|
|
tags[i]=$tag
|
|
|
|
tags[i]=$tag
|
|
|
|
fits_ram=$(awk -v m="$mem" -v b="$budget" 'BEGIN{print (m<=b)}')
|
|
|
|
fits_ram=$(awk -v m="$mem" -v b="$budget" 'BEGIN{print (m<=b)}')
|
|
|
|
|
|
|
|
fits_hard_ram=$(awk -v m="$mem" -v b="$hard_budget" 'BEGIN{print (m<=b)}')
|
|
|
|
fits_gpu=$(awk -v m="$mem" -v b="$gpu_budget" -v u="$gpu_usable" 'BEGIN{print (u && m<=b)}')
|
|
|
|
fits_gpu=$(awk -v m="$mem" -v b="$gpu_budget" -v u="$gpu_usable" 'BEGIN{print (u && m<=b)}')
|
|
|
|
|
|
|
|
fits_hard_gpu=$(awk -v m="$mem" -v b="$gpu_hard" -v u="$gpu_usable" 'BEGIN{print (u && m<=b)}')
|
|
|
|
if [ "$fits_gpu" = 1 ]; then status[i]="fits"; speed[i]="fast (GPU)"
|
|
|
|
if [ "$fits_gpu" = 1 ]; then status[i]="fits"; speed[i]="fast (GPU)"
|
|
|
|
|
|
|
|
elif [ "$fits_hard_gpu" = 1 ]; then status[i]="tight"; speed[i]="tight (GPU)"
|
|
|
|
elif [ "$fits_ram" = 1 ] && [ "$cpu_ok" = yes ]; then status[i]="fits"; speed[i]="ok (CPU)"
|
|
|
|
elif [ "$fits_ram" = 1 ] && [ "$cpu_ok" = yes ]; then status[i]="fits"; speed[i]="ok (CPU)"
|
|
|
|
elif [ "$fits_ram" = 1 ]; then status[i]="slow"; speed[i]="slow (CPU)"
|
|
|
|
elif [ "$fits_ram" = 1 ]; then status[i]="slow"; speed[i]="slow (CPU)"
|
|
|
|
|
|
|
|
elif [ "$fits_hard_ram" = 1 ]; then status[i]="tight"; speed[i]="tight (CPU)"
|
|
|
|
else status[i]="too big"; speed[i]="-"; fi
|
|
|
|
else status[i]="too big"; speed[i]="-"; fi
|
|
|
|
[ "${status[$i]}" = fits ] && rec=$i # sorted by rank, so last "fits" wins
|
|
|
|
[ "${status[$i]}" = fits ] && rec=$i # sorted by rank, so last "fits" wins
|
|
|
|
done
|
|
|
|
done
|
|
|
@@ -206,7 +218,10 @@ if [ -z "$FORCE_MODEL" ]; then
|
|
|
|
[[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -ge 1 ] && [ "$choice" -le "${#rows[@]}" ] || { echo "Invalid choice." >&2; exit 1; }
|
|
|
|
[[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -ge 1 ] && [ "$choice" -le "${#rows[@]}" ] || { echo "Invalid choice." >&2; exit 1; }
|
|
|
|
sel=$((choice-1))
|
|
|
|
sel=$((choice-1))
|
|
|
|
MODEL=${tags[$sel]}
|
|
|
|
MODEL=${tags[$sel]}
|
|
|
|
if [ "${status[$sel]}" = "too big" ] || [ "${status[$sel]}" = "slow" ]; then
|
|
|
|
if [ "${status[$sel]}" = "tight" ]; then
|
|
|
|
|
|
|
|
echo "Warning: $MODEL is 'tight' on this machine (fits, but leaves little headroom - close other apps first)."
|
|
|
|
|
|
|
|
if [ "$ASSUME_YES" != 1 ]; then read -r -p "Continue anyway? [y/N] " a; [[ "$a" =~ ^[Yy] ]] || exit 1; fi
|
|
|
|
|
|
|
|
elif [ "${status[$sel]}" = "too big" ] || [ "${status[$sel]}" = "slow" ]; then
|
|
|
|
echo "Warning: $MODEL is '${status[$sel]}' on this machine (may swap, crawl, or crash)."
|
|
|
|
echo "Warning: $MODEL is '${status[$sel]}' on this machine (may swap, crawl, or crash)."
|
|
|
|
if [ "$ASSUME_YES" != 1 ]; then read -r -p "Continue anyway? [y/N] " a; [[ "$a" =~ ^[Yy] ]] || exit 1; fi
|
|
|
|
if [ "$ASSUME_YES" != 1 ]; then read -r -p "Continue anyway? [y/N] " a; [[ "$a" =~ ^[Yy] ]] || exit 1; fi
|
|
|
|
fi
|
|
|
|
fi
|
|
|
@@ -219,6 +234,19 @@ if awk -v d="$sel_disk" -v f="$disk_gb" 'BEGIN{exit !(d*2>f)}'; then
|
|
|
|
NOTES+=("Low disk space: ${disk_gb} GB free, the build needs roughly 2x the model size (${sel_disk} GB).")
|
|
|
|
NOTES+=("Low disk space: ${disk_gb} GB free, the build needs roughly 2x the model size (${sel_disk} GB).")
|
|
|
|
fi
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ------------------------------------------------------------ host port
|
|
|
|
|
|
|
|
# Default 11434 (Ollama's standard); if something else (e.g. a host Ollama)
|
|
|
|
|
|
|
|
# already uses it, take the next free port.
|
|
|
|
|
|
|
|
if [ -n "$FORCE_PORT" ]; then PORT=$FORCE_PORT
|
|
|
|
|
|
|
|
else
|
|
|
|
|
|
|
|
for PORT in $(seq 11434 11534); do
|
|
|
|
|
|
|
|
# our own container from an earlier run doesn't count as busy
|
|
|
|
|
|
|
|
[ "$(docker port "$CONTAINER_NAME" 11434/tcp 2>/dev/null | head -1 | sed 's/.*://')" = "$PORT" ] && break
|
|
|
|
|
|
|
|
port_busy "$PORT" || break
|
|
|
|
|
|
|
|
done
|
|
|
|
|
|
|
|
[ "$PORT" != 11434 ] && NOTES+=("Port 11434 is already in use on this machine, so the AI container will use port $PORT.")
|
|
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------- output
|
|
|
|
# ------------------------------------------------------------------- output
|
|
|
|
USE_GPU=$gpu_usable
|
|
|
|
USE_GPU=$gpu_usable
|
|
|
|
sed -e "s|@@MODEL@@|$MODEL|g" -e "s|@@CONTEXT@@|$CONTEXT|g" Dockerfile.template > Dockerfile
|
|
|
|
sed -e "s|@@MODEL@@|$MODEL|g" -e "s|@@CONTEXT@@|$CONTEXT|g" Dockerfile.template > Dockerfile
|
|
|
|