Vai al contenuto

LocalAI su macOS: accelerazione GPU con Metal, passo passo

LocalAI v4.9.0 su macOS Apple Silicon con accelerazione GPU via Metal: installazione, configurazione del modello e verifica del caricamento su GPU.

Testato il: 2026-09-13 Ambiente: macOS 26.6.2 (Tahoe) · Apple M2 (arm64) · LocalAI v4.9.0 (Homebrew) · Go 1.27.1 (solo per build da sorgente)

Prerequisiti

  • Un Mac Apple Silicon (M1, M2, M3, M4, M5): GPU e CPU condividono la stessa memoria unificata, ed è lì che Metal lavora meglio
  • Homebrew installato e aggiornato
  • Almeno 8 GB di RAM unificata e una decina di GB di spazio libero: il primo avvio scarica il backend llama.cpp e il modello (~900 MB)
  • Xcode Command Line Tools (xcode-select --install), obbligatori solo se vuoi compilare da sorgente
Docker su macOS resta su CPU — Docker Desktop gira in una VM Linux che non ha accesso alla GPU Metal: i container LocalAI restano su CPU e la guida non funziona. Non usare Docker per questa guida: serve il binario nativo.

1. Verifica hardware e sistema

Apri Terminale e controlla architettura, versione di macOS e GPU:

uname -m
sw_vers
system_profiler SPDisplaysDataType | grep -E "Chipset|Metal"

Risultato atteso: arm64 come architettura, una versione di macOS tipo 26.6.2, e nel report della GPU le righe Chipset Model: Apple M2 e Metal Support: Metal 3. Se uname -m risponde x86_64, stai girando sotto Rosetta: apri Terminale in modalità nativa (arm64) prima di continuare. Se hai un Mac Intel, la guida arriva in fondo con le limitazioni del caso.

2. Installa LocalAI con Homebrew

brew install localai
local-ai --version

Risultato atteso: Homebrew installa il binario local-ai e local-ai --version risponde v4.9.0 (la release stabile di agosto 2026). La formula esiste per macOS arm64 e per Intel. In alternativa puoi scaricare il DMG da GitHub releases (un launcher nella menu bar che gestisce il server) o il binario diretto, ma Homebrew resta la strada più semplice da aggiornare.

Se vuoi la build da sorgente (per esempio per testare una modifica al backend), il percorso è git clone del repository di LocalAI seguito da make build: richiede Go (la formula Homebrew lo compila con Go 1.27.1) e Xcode con il toolchain Metal, e produce il binario local-ai nella directory del repo.

3. Avvia il server e installa un modello

Crea una directory di lavoro e avvia LocalAI con un modello piccolo del catalogo. Il comando run installa il modello e fa partire il server in primo piano:

mkdir -p ~/localai-mps && cd ~/localai-mps
DEBUG=true local-ai run llama-3.2-1b-instruct:q4_k_m

Risultato atteso, nell'ordine: il backend llama-cpp viene scaricato (su Apple Silicon la build con supporto Metal), poi il file GGUF (~800 MB per llama-3.2-1b in Q4_K_M), poi i log con API ready on http://localhost:8080 e model ready. La variabile DEBUG=true rende i log molto più verbosi: è quello che ci serve al passo 4, e in produzione puoi toglierla.

Con il server acceso, in un secondo terminale puoi chattare subito:

local-ai chat --model llama-3.2-1b-instruct:q4_k_m

Risultato atteso: un prompt interattivo che risponde in pochi secondi. La web UI è su localhost:8080: lì trovi la chat, i modelli installati e lo stato del sistema.

4. Forza il caricamento su GPU

Il gotcha: senza `gpu_layers` resti in CPU — Le entry del catalogo LocalAI impostano `f16: true` ma **non** `gpu_layers`: senza quel valore llama.cpp carica il modello in CPU anche se il backend Metal c'è. È il motivo per cui molti credono che "Metal non funziona". Verifica la configurazione generata:
ls -la ~/localai-mps/models/

Risultato atteso: nella directory trovi il file .gguf e un YAML di configurazione (es. llama-3.2-1b-instruct-q4_k_m.yaml). Aprilo e aggiungi gpu_layers a livello top, vicino a f16: true. Il YAML generato ha anche campi di template e opzioni: lasciali intatti. Estratto minimo dei campi che contano:

name: llama-3.2-1b-instruct
backend: llama-cpp
f16: true
gpu_layers: 999
context_size: 8192

999 non è un errore di battitura: significa "scarica su GPU tutti i layer che il modello ha" (llama-3.2-1b ne ha 16, quindi 999 equivale a 16). I valori più bassi servono solo quando il modello non entra in memoria. Riavvia il server (Ctrl+C e rilancia il comando del passo 3) e guarda i log del caricamento.

Risultato atteso nei log con DEBUG=true:

ggml_metal_device_init: GPU name:   MTL0
ggml_metal_device_init: has unified memory    = true
llm_load_tensors: offloading 16 repeating layers to GPU
llm_load_tensors: offloaded 16/16 layers to GPU
Il segnale stabile da cercare — Il formato esatto delle righe `ggml_metal*` cambia tra le versioni di llama.cpp impacchettate da LocalAI, il verdetto `offloaded N/N layers to GPU` invece è stabile: cerca quello. Se vedi `offloaded 0/16`, il modello è rimasto in CPU e `gpu_layers` non è stato letto.

5. Chiama l'API

LocalAI espone un'API compatibile con OpenAI. Verifica i modelli caricati:

curl http://localhost:8080/v1/models

Risultato atteso: un JSON con l'id del modello, llama-3.2-1b-instruct:q4_k_m (usa quell'id esatto nelle chiamate, è quello che il server espone). Poi una chat completion:

curl http://localhost:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model": "llama-3.2-1b-instruct:q4_k_m", "messages": [{"role": "user", "content": "Racconta una barzelletta in tre righe"}], "max_tokens": 256}'

Risultato atteso: un JSON OpenAI-compatible con la risposta in choices[0].message.content. Se risponde, il server è pronto per qualsiasi client OpenAI (LangChain, Open WebUI, script custom): basta cambiare base URL e chiave API.

6. Confronta CPU e GPU

Ora misura quanto vale Metal. Metti gpu_layers: 0 nel YAML, riavvia e cronometra una generazione:

time curl http://localhost:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model": "llama-3.2-1b-instruct:q4_k_m", "messages": [{"role": "user", "content": "Scrivi una poesia di quattro versi su Roma"}], "max_tokens": 128}'

Poi rimetti gpu_layers: 999, riavvia e rilancia lo stesso identico comando. Risultato atteso: il tempo totale della richiesta GPU è inferiore, ma con un modello da 1B il divario sui token generati è contenuto: il modello è piccolo e anche la CPU lo sforna veloce. Il guadagno grosso del Metal si vede nel prompt processing e con modelli da 3B in su. Se vuoi una prova più netta, installa qwen3-4b (local-ai run qwen3-4b) e ripeti il confronto: lì il divario CPU/GPU è difficile da non vedere.

7. MPS o Metal: la distinzione che conta

Se sei arrivato qui cercando un flag "MPS" da attivare, chiudiamo con la precisazione onesta: in LocalAI non esiste un backend chiamato MPS. Su macOS l'accelerazione passa dal backend Metal di llama.cpp (ggml-metal), che è il backend GPU consigliato per Apple Silicon e viene scelto in automatico dalla rilevazione hardware di LocalAI.

La confusione nasce da tre cose che si chiamano quasi uguale e non sono la stessa cosa:

  • Metal Performance Shaders (MPS) è la libreria di compute proprietaria di Apple. Il backend Metal di llama.cpp non la usa per i calcoli principali: usa shader Metal scritti dal progetto (i kernel mul_mm e mul_mat_vec in Metal Shading Language) e istruzioni simdgroup. Non c'è un flag "MPS" da attivare da nessuna parte.
  • torch.backends.mps è il device MPS di PyTorch. Se arrivi da PyTorch, "MPS" lì indica la GPU Apple: stesso hardware, concetto diverso, nessuna corrispondenza con LocalAI.
  • llama.cpp ha avuto un backend MPS (compilato con GGML_MPS, basato su MPSMatrixMultiplication): è stato rimosso anni fa a favore del backend Metal unificato. Per questo nei log cerchi ggml_metal, non "mps".

Non serve nemmeno toccare LOCALAI_FORCE_META_BACKEND_CAPABILITY: quella variabile serve a forzare i backend CUDA, ROCm o SYCL dove la rilevazione automatica sbaglia, e su macOS non ha un valore "metal". La rilevazione automatica sceglie la build Metal del backend da sola.

E se vuoi spremere la M-series, c'è anche il backend MLX di LocalAI (supportato da agosto 2025): MLX è il framework array di Apple, costruito sopra Metal, e per alcuni modelli su Apple Silicon è più veloce di llama.cpp. È il secondo esperimento da fare, non il primo: MLX copre meno formati e la compatibilità GGUF è più stretta.

Verifica

Hai la GPU attiva se e solo se:

  1. local-ai --version risponde v4.9.0 (o la release corrente)
  2. I log del caricamento mostrano ggml_metal* e offloaded 16/16 layers to GPU (N/N, non 0/N)
  3. curl http://localhost:8080/v1/models risponde con l'id del modello
  4. /v1/chat/completions risponde in pochi secondi
  5. Il confronto del passo 6 con gpu_layers: 0 è più lento di quello con gpu_layers: 999

Se il punto 2 fallisce ma gli altri passano, il server funziona: sei solo in CPU. Correggi gpu_layers nel YAML e riavvia.

Limiti noti

  • Il guadagno dipende dal chip e dalla RAM unificata. Su un MacBook Air M1 base, un modello 1B va veloce anche in CPU: il test onesto è su modelli 3-8B, dove Metal fa la differenza vera.
  • Non tutte le quantizzazioni hanno kernel Metal. Formati sperimentali come TQ2_0 abortiscono con errori tipo "not implemented in ggml-metal-device.cpp". Resta su Q4_K_M, Q5_K_M o Q8_0.
  • La memoria è condivisa. Un modello grande compete con browser, IDE e resto del sistema: se il Mac inizia a swappare, Metal non aiuta.
  • Docker su macOS non passa la GPU. I container LocalAI girano su CPU: usa il binario nativo, come in questa guida.
  • Intel Mac. macOS Tahoe è l'ultima versione che li supporta, i binari ufficiali LocalAI sono arm64 e la GPU AMD dei Mac Intel dà risultati modesti. Su Intel la strada è compilare da sorgente e le aspettative vanno ridimensionate.
  • Le entry del catalogo non sempre impostano gpu_layers. Ogni volta che aggiungi un modello, verifica il YAML generato: il caricamento su GPU non è garantito dal solo fatto che il backend Metal esista.
  • Versioni. La guida documenta LocalAI v4.9.0 (settembre 2026). Le righe di log di llama.cpp cambiano tra versioni: il segnale stabile da cercare è offloaded N/N layers to GPU.
Condividi