Installer un LLM chez soi - llama.cpp avec Gemma 4, Mistral Small 3.2 et Qwen 3 Coder
Installer llama.cpp
curl -LsSf https://llama.app/install.sh | sh
Ou clone repo :
git clone https://github.com/ggml-org/llama.cpp
Installer redis :
pacman -S redis
Script llama.cpp.sh qui fait le café à placé dans ~/Scripts/ :
#!/bin/bash
set -euo pipefail
# Configuration par défaut
LLAMA_SERVER="/home/perru/Git/models/llama.cpp/build/bin/llama-server"
HOST="0.0.0.0"
PORT="8080"
# Contexte centralisé
#CTX_SIZE="32768" # 32K tokens
#CTX_SIZE="65536" # 64K tokens
CTX_SIZE="131072" # 128K tokens
#CTX_SIZE="262144" # 256K tokens
#CTX_SIZE="524288" # 512K tokens
THREADS="16"
# Fichiers de configuration persistants
PIDFILE="/tmp/llama-manager.pid"
MODELFILE="/tmp/llama-manager.model"
LOGFILE="/tmp/llama-server.log"
PERSISTENT_STATE="$HOME/.llama_manager_state"
# Configuration Redis
REDIS_HOST="localhost"
REDIS_PORT="6379"
REDIS_PREFIX="llama_conv_"
# Timeout pour le démarrage
START_TIMEOUT=180
# Déclaration des tableaux associatifs
declare -A MODELS
declare -A HF_REPOS
declare -A HF_FILES
declare -A MODEL_DIRS
# ------------------------------------------------------------
# Modèles
# ------------------------------------------------------------
# Qwen
MODELS[qwen]="$HOME/models/qwen3-coder-30b/Qwen3-Coder-30B-A3B-Instruct-Q4_K_M.gguf"
HF_REPOS[qwen]="unsloth/Qwen3-Coder-30B-A3B-Instruct-GGUF"
HF_FILES[qwen]="Qwen3-Coder-30B-A3B-Instruct-Q4_K_M.gguf"
MODEL_DIRS[qwen]="$HOME/models/qwen3-coder-30b"
# Mistral
MODELS[mistral]="$HOME/models/mistral-small-3.2-24b/Mistral-Small-3.2-24B-Instruct-2506-UD-Q4_K_XL.gguf"
HF_REPOS[mistral]="unsloth/Mistral-Small-3.2-24B-Instruct-2506-GGUF"
HF_FILES[mistral]="Mistral-Small-3.2-24B-Instruct-2506-UD-Q4_K_XL.gguf"
MODEL_DIRS[mistral]="$HOME/models/mistral-small-3.2-24b"
# Gemma
MODELS[gemma]="$HOME/models/gemma-4-26b-a4b/gemma-4-26B-A4B-it-UD-IQ4_XS.gguf"
HF_REPOS[gemma]="unsloth/gemma-4-26B-A4B-it-GGUF"
HF_FILES[gemma]="gemma-4-26B-A4B-it-UD-IQ4_XS.gguf"
MODEL_DIRS[gemma]="$HOME/models/gemma-4-26b-a4b"
# ============================================================
# Fonctions utilitaires
# ============================================================
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
}
# ============================================================
# Fonctions d'affichage du contexte
# ============================================================
show_context_info() {
log "Context Size: $CTX_SIZE tokens"
log "Context Size: $((CTX_SIZE / 1024))K tokens"
# Estimation de la mémoire utilisée par le contexte
local estimated_mem_mb=$((CTX_SIZE * 4 / 1024)) # Approximation 4 bytes par token
log "Estimated VRAM usage: ~${estimated_mem_mb} MB"
# Vérification si le contexte est trop grand
if [ "$CTX_SIZE" -gt "131072" ]; then
log "WARNING: Context size > 128K tokens - may cause memory issues"
elif [ "$CTX_SIZE" -gt "65536" ]; then
log "INFO: Context size > 64K tokens - running with caution"
else
log "INFO: Context size within normal range"
fi
}
# ============================================================
# Mise à jour de llama.cpp
# ============================================================
update_llama_cpp() {
local llama_cpp_dir="/home/perru/Git/models/llama.cpp"
log "Updating llama.cpp..."
# Vérifier si le répertoire existe
if [[ ! -d "$llama_cpp_dir" ]]; then
log "ERROR: llama.cpp directory not found at $llama_cpp_dir"
log "Please clone the repository first:"
log " git clone https://github.com/ggerganov/llama.cpp.git $llama_cpp_dir"
exit 1
fi
# Aller dans le répertoire
cd "$llama_cpp_dir" || exit 1
# Récupérer les dernières modifications
git fetch origin
# Vérifier si une mise à jour est disponible
local current_commit=$(git rev-parse HEAD)
local upstream_commit=$(git rev-parse origin/main)
if [[ "$current_commit" != "$upstream_commit" ]]; then
log "New updates available for llama.cpp"
log "Current commit: $current_commit"
log "Upstream commit: $upstream_commit"
# Faire le pull
if ! git pull origin main; then
log "ERROR: Failed to pull updates from origin"
exit 1
fi
log "llama.cpp updated successfully"
else
log "llama.cpp is already up to date"
fi
# Build avec les mêmes paramètres que ton setup
log "Building llama.cpp with Vulkan support..."
# Nettoyer le build précédent
rm -rf build
# Créer le build avec les mêmes options que ton setup
cmake -B build \
-DGGML_VULKAN=ON \
-DCMAKE_BUILD_TYPE=Release
# Compiler
cmake --build build -j$(nproc)
# Vérifier que le binaire a été créé
if [[ ! -f "build/bin/llama-server" ]]; then
log "ERROR: Failed to build llama-server"
exit 1
fi
log "llama.cpp built successfully with Vulkan support"
# Revenir au répertoire original
cd - > /dev/null || exit 1
log "llama.cpp update completed"
}
# ============================================================
# PID
# ============================================================
get_pid() {
if [[ -f "$PIDFILE" ]]; then
cat "$PIDFILE"
fi
}
server_running() {
local pid
pid="$(get_pid || true)"
[[ -n "$pid" ]] &&
kill -0 "$pid" 2>/dev/null
}
# ============================================================
# Redis - Gestion des conversations
# ============================================================
# Sauvegarder une conversation
save_conversation() {
local session_id="$1"
local conversation_data="$2"
if command -v redis-cli >/dev/null 2>&1; then
redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" SET "${REDIS_PREFIX}${session_id}" "$conversation_data" 2>/dev/null || true
fi
}
# Charger une conversation
load_conversation() {
local session_id="$1"
if command -v redis-cli >/dev/null 2>&1; then
redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" GET "${REDIS_PREFIX}${session_id}" 2>/dev/null || true
fi
}
# Supprimer une conversation
delete_conversation() {
local session_id="$1"
if command -v redis-cli >/dev/null 2>&1; then
redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" DEL "${REDIS_PREFIX}${session_id}" 2>/dev/null || true
fi
}
# Liste toutes les conversations actives
list_conversations() {
if command -v redis-cli >/dev/null 2>&1; then
redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" KEYS "${REDIS_PREFIX}*" 2>/dev/null | sed "s/${REDIS_PREFIX}//" || true
fi
}
# Générer un ID de session
generate_session_id() {
echo "session_$(date +%s)_$(openssl rand -hex 4)"
}
# ============================================================
# Persistance - Nouvelles fonctions
# ============================================================
save_state() {
local model_name="$1"
local pid="$2"
# Sauvegarder l'état dans un fichier persistant
cat > "$PERSISTENT_STATE" << EOF
MODEL=$model_name
PID=$pid
HOST=$HOST
PORT=$PORT
CTX_SIZE=$CTX_SIZE
THREADS=$THREADS
EOF
}
load_state() {
if [[ -f "$PERSISTENT_STATE" ]]; then
# Charger les variables depuis le fichier
source "$PERSISTENT_STATE" 2>/dev/null || true
# Vérifier si le modèle est toujours configuré
if [[ -n "${MODELS[$MODEL]+x}" ]]; then
log "Loading persistent state: $MODEL"
return 0
fi
fi
return 1
}
# ============================================================
# Stop - CORRECTION
# ============================================================
stop_server() {
local pid
pid="$(get_pid || true)"
if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then
log "Stopping llama-server (PID $pid)..."
# Tuer le processus et ses enfants
pkill -P "$pid" 2>/dev/null || true # Tuer les enfants
kill -TERM "$pid" 2>/dev/null || true # Tuer le processus principal
# Attendre un peu que le processus se termine
sleep 2
# Vérifier si le processus est toujours actif
if kill -0 "$pid" 2>/dev/null; then
log "Force killing llama-server (PID $pid)..."
kill -9 "$pid" 2>/dev/null || true
fi
fi
# Nettoyer le fichier PID
rm -f "$PIDFILE"
# Nettoyer les fichiers temporaires
rm -f "/tmp/llama-server-*.log" 2>/dev/null || true
# Vérifier qu'aucun processus ne reste en arrière-plan
pkill -f "llama-server" 2>/dev/null || true
# Effacer l'état persistant
rm -f "$PERSISTENT_STATE" 2>/dev/null || true
}
# ============================================================
# Force stop - pour s'assurer que tout est arrêté
# ============================================================
force_stop() {
log "Force stopping all llama-server processes..."
# Tuer tous les processus llama-server
pkill -f "llama-server" 2>/dev/null || true
# Nettoyer les fichiers
rm -f "$PIDFILE" "/tmp/llama-server-*.log" 2>/dev/null || true
# Effacer l'état persistant
rm -f "$PERSISTENT_STATE" 2>/dev/null || true
# Vérifier l'état
if pgrep -f "llama-server" > /dev/null; then
log "WARNING: Some llama-server processes still running"
pgrep -f "llama-server"
else
log "All llama-server processes stopped"
fi
}
# ============================================================
# GPU Information
# ============================================================
show_gpu_info() {
if ! command -v vulkaninfo >/dev/null 2>&1; then
echo " vulkaninfo: unavailable"
return
fi
vulkaninfo --summary 2>/dev/null |
grep -E 'deviceName|driverName|driverInfo|deviceType' |
head -10 ||
true
}
show_gpu_memory() {
if [[ -r /sys/class/drm/card1/device/mem_info_vram_total ]]; then
local total used free
total="$(cat /sys/class/drm/card1/device/mem_info_vram_total)"
used="$(cat /sys/class/drm/card1/device/mem_info_vram_used)"
total=$((total / 1024 / 1024))
used=$((used / 1024 / 1024))
free=$((total - used))
echo " VRAM total : ${total} MiB"
echo " VRAM used : ${used} MiB"
echo " VRAM free : ${free} MiB"
else
echo " VRAM information unavailable"
fi
}
# ============================================================
# Hugging Face update
# ============================================================
update_model() {
local name="$1"
local repo="${HF_REPOS[$name]}"
local file="${HF_FILES[$name]}"
local dir="${MODEL_DIRS[$name]}"
log "Checking Hugging Face model..."
log " Repository: $repo"
log " File: $file"
log
mkdir -p "$dir"
if ! command -v hf >/dev/null 2>&1; then
log "ERROR: Hugging Face CLI 'hf' not found."
log
log "Install it with:"
log " curl -LsSf https://hf.co/cli/install.sh | bash"
exit 1
fi
if ! hf download "$repo" "$file" --local-dir "$dir"; then
log "ERROR: failed to download model from Hugging Face."
exit 1
fi
log
log "✓ Model checked/updated"
}
# ============================================================
# Model validation
# ============================================================
check_model() {
local name="$1"
local model="${MODELS[$name]}"
if [[ ! -f "$model" ]]; then
log
log "ERROR: model not found:"
log " $model"
log
exit 1
fi
if [[ ! -r "$model" ]]; then
log
log "ERROR: model is not readable: $model"
log
exit 1
fi
local size
size="$(du -h "$model" | awk '{print $1}')"
log "Model:"
log " Name: $name"
log " File: $model"
log " Size: $size"
}
# ============================================================
# Status
# ============================================================
status() {
local pid
pid="$(get_pid || true)"
if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then
log "llama-server: RUNNING"
log "PID: $pid"
log "URL: http://$HOST:$PORT"
if [[ -f "$MODELFILE" ]]; then
log "Model: $(cat "$MODELFILE")"
fi
log
if curl -sf \
--max-time 3 \
"http://$HOST:$PORT/health" \
>/dev/null 2>&1; then
log "Health: OK"
else
log "Health: NOT READY"
fi
# Afficher les informations du contexte
log
log "Context Info:"
show_context_info
else
log "llama-server: STOPPED"
# Essayer de charger l'état persistant
if load_state; then
log "Last model was: $MODEL"
log "Last PID was: $PID"
fi
fi
log
log "GPU:"
show_gpu_info
log
log "Memory:"
show_gpu_memory
}
# ============================================================
# Afficher le status dans une fenêtre
# ============================================================
show_status_window() {
local status_output
status_output=$(status 2>&1)
# Essayer avec zenity d'abord
if command -v zenity >/dev/null 2>&1; then
echo "$status_output" | zenity --text-info --title="LLaMA Server Status" --width=600 --height=400 2>/dev/null
# Ensuite avec xterm
elif command -v xterm >/dev/null 2>&1; then
echo "$status_output" | xterm -e less 2>/dev/null
# Enfin avec une simple sortie dans le terminal
else
echo "$status_output"
fi
}
# ============================================================
# List models
# ============================================================
list_models() {
log "Available models:"
log "=================="
# Récupérer la liste des clés du tableau MODELS
for model in "${!MODELS[@]}"; do
local model_path="${MODELS[$model]}"
local size=""
if [[ -f "$model_path" ]]; then
size="$(du -h "$model_path" | awk '{print $1}')"
else
size="NOT FOUND"
fi
log " $model ($size)"
log " Path: $model_path"
log " HF Repo: ${HF_REPOS[$model]}"
log " HF File: ${HF_FILES[$model]}"
log
done
}
# ============================================================
# Start model - AMÉLIORATION DU CONTEXT
# ============================================================
start_model() {
local name="$1"
if [[ -z "${MODELS[$name]+x}" ]]; then
log "Unknown model: $name"
log
list_models
usage
fi
local model="${MODELS[$name]}"
log
log "========================================"
log " llama.cpp model manager"
log "========================================"
log
log "Selected model: $name"
log
# --------------------------------------------------------
# Update model
# --------------------------------------------------------
update_model "$name"
log
# --------------------------------------------------------
# Validate
# --------------------------------------------------------
check_model "$name"
log
# --------------------------------------------------------
# GPU information
# --------------------------------------------------------
log "GPU:"
show_gpu_info
log
log "VRAM before loading:"
show_gpu_memory
log
# --------------------------------------------------------
# Stop previous server
# --------------------------------------------------------
stop_server
sleep 1
# --------------------------------------------------------
# Remember selected model
# --------------------------------------------------------
echo "$name" > "$MODELFILE"
# --------------------------------------------------------
# Start - Utilisation du contexte centralisé
# --------------------------------------------------------
log "Starting llama-server with context size: $CTX_SIZE"
log
# Générer un nouveau fichier de log avec timestamp
LOGFILE="/tmp/llama-server-$(date +%Y%m%d-%H%M%S).log"
"$LLAMA_SERVER" \
-m "$model" \
--ctx-size "$CTX_SIZE" \
--parallel 1 \
--jinja \
--threads "$THREADS" \
--device Vulkan0 \
--host "$HOST" \
--port "$PORT" \
>"$LOGFILE" 2>&1 &
local pid=$!
echo "$pid" > "$PIDFILE"
# Sauvegarder l'état persistant
save_state "$name" "$pid"
log "PID: $pid"
log "Waiting for server..."
# --------------------------------------------------------
# Wait for health
# --------------------------------------------------------
for ((i=1; i<=START_TIMEOUT; i++)); do
if curl -sf \
--max-time 2 \
"http://$HOST:$PORT/health" \
>/dev/null 2>&1; then
log
log "✓ $name is ready"
log
log "URL: http://$HOST:$PORT"
log "PID: $pid"
log "Log: $LOGFILE"
log
log "VRAM after loading:"
show_gpu_memory
return
fi
sleep 1
done
log "ERROR: Server failed to start within $START_TIMEOUT seconds"
exit 1
}
# ============================================================
# Afficher l'aide
# ============================================================
usage() {
cat << EOF
Usage: $0 [command] [options]
Commands:
start Start a model
stop Stop the server
force-stop Force stop all servers
status Show server status
list-models List all available models
list-conversations List all conversations
save-conversation Save conversation
load-conversation Load conversation
delete-conversation Delete conversation
update-llama Update llama.cpp to latest version
Examples:
$0 start qwen
$0 status
$0 list-models
$0 list-conversations
$0 update-llama
$0 save-conversation session_123 "User: Hello\nAI: Hi there!"
$0 load-conversation session_123
EOF
exit 1
}
# ============================================================
# Point d'entrée principal
# ============================================================
main() {
if [[ $# -eq 0 ]]; then
usage
fi
case "$1" in
start)
if [[ $# -lt 2 ]]; then
log "ERROR: Missing model name"
usage
fi
start_model "$2"
;;
stop)
stop_server
;;
force-stop)
force_stop
;;
status)
# Si le script est appelé directement avec status, afficher dans la fenêtre
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
show_status_window
else
status
fi
;;
list-models)
list_models
;;
list-conversations)
log "Active conversations:"
list_conversations
;;
save-conversation)
if [[ $# -lt 3 ]]; then
log "ERROR: Missing session ID or data"
usage
fi
save_conversation "$2" "$3"
log "Conversation saved with ID: $2"
;;
load-conversation)
if [[ $# -lt 2 ]]; then
log "ERROR: Missing session ID"
usage
fi
local data=$(load_conversation "$2")
if [[ -n "$data" ]]; then
log "Conversation data for $2:"
echo "$data"
else
log "No conversation found for ID: $2"
fi
;;
delete-conversation)
if [[ $# -lt 2 ]]; then
log "ERROR: Missing session ID"
usage
fi
delete_conversation "$2"
log "Conversation deleted: $2"
;;
update-llama)
update_llama_cpp
;;
*)
log "Unknown command: $1"
usage
;;
esac
}
# Exécuter le script
main "$@"
Et le menu.xml de labwc si jamais :
<?xml version="1.0" encoding="utf-8"?>
<openbox_menu xmlns="http://openbox.org/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://openbox.org/ file:///usr/share/openbox/menu.xsd">
<menu id="root-menu" label="Menu">
<!-- Applications principales -->
<item label="Terminal">
<action name="Execute"><command>foot</command></action>
</item>
<item label="Navigateur">
<action name="Execute"><command>librewolf</command></action>
</item>
<item label="Fichiers">
<action name="Execute"><command>thunar</command></action>
</item>
<item label="Éditeur">
<action name="Execute"><command>subl</command></action>
</item>
<item label="Lutris (Proton)">
<action name="Execute">
<command>sh -c 'LUTRIS_ENABLE_PROTON=1 lutris'</command>
</action>
</item>
<item label="Slippi Online">
<action name="Execute"><command>slippi-launcher</command></action>
</item>
<item label="Steam (minimal)">
<action name="Execute">
<command>sh -c 'steam-native -cef-disable-gpu-compositing -cef-disable-gpu'</command>
</action>
</item>
<separator/>
<!-- Menu IA -->
<menu id="ia-menu" label="IA">
<item label="Start Qwen">
<action name="Execute">
<command>/home/perru/Scripts/llama.cpp.sh start qwen</command>
</action>
</item>
<item label="Start Mistral">
<action name="Execute">
<command>/home/perru/Scripts/llama.cpp.sh start mistral</command>
</action>
</item>
<item label="Start Gemma">
<action name="Execute">
<command>/home/perru/Scripts/llama.cpp.sh start gemma</command>
</action>
</item>
<separator/>
<item label="Status">
<action name="Execute">
<command>/home/perru/Scripts/llama.cpp.sh status</command>
</action>
</item>
<item label="Stop Server">
<action name="Execute">
<command>/home/perru/Scripts/llama.cpp.sh stop</command>
</action>
</item>
<item label="Force Stop">
<action name="Execute">
<command>/home/perru/Scripts/llama.cpp.sh force-stop</command>
</action>
</item>
</menu>
<separator/>
<!-- Applications auto -->
<menu id="applications" label="Applications"
execute="labwc-menu-generator -p -I" />
<separator/>
<!-- Outils -->
<item label="Capture écran">
<action name="Execute">
<command>sh -c 'grim -g "$(slurp)"'</command>
</action>
</item>
<separator/>
<!-- Système -->
<item label="Recharger labwc">
<action name="Reconfigure"/>
</item>
<item label="Relancer Waybar">
<action name="Execute">
<command>killall -SIGUSR2 waybar</command>
</action>
</item>
<separator/>
<!-- Session -->
<menu id="session" label="Session">
<item label="Quitter">
<action name="Exit"/>
</item>
<item label="Redémarrer">
<action name="Execute">
<command>systemctl -i reboot</command>
</action>
</item>
<item label="Éteindre">
<action name="Execute">
<command>systemctl -i poweroff</command>
</action>
</item>
</menu>
</menu>
</openbox_menu>
Y a plus qu'à tester : http://localhost:8080/