TuBrief
Subscribed Channels
Videos
Community

Configurations d'ingénierie pour sauver un LLM local qui plante sur Docker sous Mac

TuBrief Editorial
August 14, 2026
0
Computing/Software

Written with AI assistance from the source video. The video is the authority.

Français한국어EnglishEspañol中文العربيةDeutschPortuguêsहिन्दीРусскийBahasa Indonesia日本語

Related Video

PewDiePie est ingénieur logiciel maintenant...6:29

PewDiePie est ingénieur logiciel maintenant...

Better Stack

More from the community

사내 시스템에 llm api 붙일 때 마주하는 현실적인 한계와 대응법

September 13, 2026

레거시 백엔드에 GPT-6 Astra 붙일 때 예산 승인과 보안 통과를 먼저 끝내는 법이 있습니다

September 13, 2026

에이전트끼리 대화하다 6천만 원 청구서가 나오는 이유

September 13, 2026

사내 RAG 벡터 검색에 Okta 권한 필터를 직접 거는 방법

September 13, 2026

브라우저 에이전트에게 내 구글 계정을 통째로 넘기면 안 되는 이유

September 12, 2026

Apple Won the AI Race

September 12, 2026

Comments (0)

Log in to leave a comment

No posts yet

© 2026 . All rights reserved.

TuBrief
Subscribed Channels
Videos
Community
Log in

Configurations d'ingénierie pour sauver un LLM local qui plante sur Docker sous Mac

Lorsque vous téléchargez le code d'un espace de travail IA open source et le lancez dans Docker sur Mac, vous vous heurtez à un mur dès le premier prompt. Bien que vous utilisiez visiblement une puce de la série M, les ventilateurs s'affolent pour un résultat médiocre d'une dizaine de tokens par seconde. C'est parce que la couche de virtualisation de Docker Desktop ne parvient pas à transmettre le GPU Apple Silicon (Metal) à l'intérieur du conteneur, ce qui rabat le traitement sur le CPU en calcul brut.

Si vous ne contournez pas ce goulot d'étranglement tout en associant un backend FastAPI et une base de données vectorielle locale, le conteneur finira par s'effondrer en raison de fuites de mémoire et d'un épuisement des connexions. Voici quatre configurations à modifier pour faire tourner en environnement local, avec un niveau de qualité de production, du code trouvé uniquement sur la base de ses étoiles GitHub.

Cause du repli sur le CPU et contournement de l'accélération matérielle

Docker Desktop pour macOS s'exécute sur une machine virtuelle Linux légère. Il n'existe aucune fonctionnalité permettant de transmettre directement l'API GPU Metal de l'hôte dans le conteneur du système d'exploitation invité Linux. Ollama ou llama.cpp à l'intérieur du conteneur échouent à allouer la VRAM et basculent discrètement sur le calcul CPU. C'est pourquoi un modèle Llama 3 8B atteignant 40 à 80 t/s en natif sur une M3 Max chute brutalement à environ 21,45 t/s pour l'évaluation du prompt et 12,17 t/s pour la génération de tokens dès qu'il entre dans Docker.

`
[Mécanisme de repli sur le CPU dans le conteneur de virtualisation]
+-----------------------------------------------------------------------+
| Docker Container (Linux Guest OS) |
| +---------------------+ |
| | Local LLM Engine | --(VRAM Allocation Req)--> [VirtGPU Missing] |
| +---------------------+ | |
| | v |
| +<--(Fallback to CPU Execution)--- [Silent slog.Debug] |
+-------------|---------------------------------------------------------+
v
[Apple Silicon Host CPU (ARM Neon/DotProd)] -> Baisse de performance

`

Le paramètre de mémoire partagée (shm) par défaut pose également problème. Avec le quota initial de 64 Mo, une erreur de bus (Bus Error) se produit immédiatement lors de calculs tensoriels à grande échelle. Ouvrez ~/.docker/daemon.json pour étendre la mémoire partagée et lever les limites de ressources.

`json
{
"builder": {
"gc": {
"defaultKeepStorage": "20GB",
"enabled": true
}
},
"experimental": false,
"default-shm-size": "8g"
}

`

`yaml
version: '3.8'

services:
llm-inference-engine:
image: ollama/ollama:latest
container_name: local_llm_engine
shm_size: '16gb'
ipc: host
deploy:
resources:
limits:
cpus: '8.0'
memory: 24G
reservations:
cpus: '4.0'
memory: 12G
ports:
- "11434:11434"
volumes:
- ollama_storage:/root/.ollama

volumes:
ollama_storage:

`

Pour exploiter l'accélération GPU à l'intérieur du conteneur, vous devez passer à Podman en utilisant le moniteur de machine virtuelle libkrun et le pilote krunkit. Il s'agit d'une méthode (Virtio-GPU Venus) qui relaie les requêtes de calcul Vulkan du conteneur vers l'API Metal de macOS hôte. Cela permet de ramener la vitesse de traitement jusqu'à 75 % des performances Metal natives.

  1. Installez krunkit et Podman via Homebrew.
    brew tap slp/krunkit && brew install krunkit podman
  2. Lancez une machine de 8 cœurs et 32 Go de RAM basée sur le fournisseur libkrun.
    export CONTAINERS_MACHINE_PROVIDER="libkrun" && podman machine init --cpus 8 --memory 32768 && podman machine start
  3. Vérifiez que le pilote est bien chargé à l'intérieur de la machine virtuelle.
    podman machine ssh "ls -la /dev/dri"

`dockerfile
FROM fedora:40

RUN dnf -y install dnf-plugins-core &&
dnf -y copr enable slp/mesa-krunkit fedora-40-aarch64 &&
dnf -y install mesa-vulkan-drivers vulkan-loader vulkan-tools &&
dnf -y downgrade mesa-vulkan-drivers.aarch64 --repo copr:copr.fedorainfracloud.org:slp:mesa-krunkit &&
dnf clean all

ENV GGML_VULKAN=1

`

Si les règles d'infrastructure vous obligent à n'utiliser que les calculs CPU, optez pour le format de quantification Q4_0_4_4 adapté aux instructions vectorielles ARMv8.4-A DotProduct et Neon. Cela maintient la vitesse d'évaluation des prompts à 50,63 t/s, réduisant le temps de traitement à moins de la moitié par rapport à une exécution Docker CPU standard.

Configuration du runtime Évaluation du prompt (t/s) Génération de tokens (t/s) Difficulté de mise en place Caractéristiques
Docker Desktop (CPU par défaut) ~21,45 ~12,17 Faible Repli sur le CPU dû aux contraintes de virtualisation
Docker Container (ARM Q4_0_4_4) ~50,63 ~14,01 Moyenne Utilisation des instructions vectorielles ARM Neon
Podman + libkrun (Vulkan Venus) ~75 % du natif ~75 % du natif Élevée Accélération Virtio-GPU à l'intérieur du conteneur
Host Native Metal + Container 100 % du natif (40~80) 100 % du natif Moyenne Structure appelant directement le moteur hôte

Suivi des fuites de mémoire FastAPI et récupération des flux interrompus

Les backends open source présentent souvent une gestion médiocre de l'état des tâches asynchrones ou du ramasse-miettes (garbage collector) de CPython. À mesure que les requêtes s'accumulent, la mémoire enfle jusqu'à ce que le processus s'arrête. Utilisez tracemalloc pour identifier les points de surconsommation de mémoire.

`python
import gc
import tracemalloc

tracemalloc.start()

def log_memory_snapshot():
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('lineno')
print("[Memory Debug] Top 5 Allocations:")
for stat in top_stats[:5]:
print(stat)

gc.collect()

`

Vous devez purger de force la mémoire au niveau de la gestion des processus. Configurez le cycle de vie pour que les workers Gunicorn libèrent automatiquement la mémoire et redémarrent toutes les 1 000 requêtes traitées.

`bash
gunicorn
--workers 4
--worker-class uvicorn.workers.UvicornWorker
--bind 0.0.0.0:8000
--max-requests 1000
--max-requests-jitter 100
--timeout 120
--keep-alive 5
--preload-app
app.main:app

`

L'ajout de --max-requests-jitter 100 évite que les 4 workers ne meurent et redémarrent simultanément en provoquant des pertes de requêtes, et les tâches sans réponse sont interrompues par un délai d'attente de 120 secondes.

Si un utilisateur ferme son navigateur en plein milieu d'une réponse, le générateur asynchrone continue de tourner en boucle, gaspillant inutilement les ressources de calcul. Vérifiez request.is_disconnected() pour quitter la boucle immédiatement dès que la connexion est coupée.

`python
import asyncio
import gc
import logging
from typing import AsyncGenerator
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import StreamingResponse

app = FastAPI(title="Production AI Workspace Backend")
logger = logging.getLogger("stream_logger")

async def robust_llm_token_stream(prompt: str, request: Request) -> AsyncGenerator[str, None]:
try:
for token_idx in range(2000):
if await request.is_disconnected():
logger.warning(f"[Stream Aborted] Client disconnected at step {token_idx}.")
break

        await asyncio.sleep(0.01)
        yield f"event: message\ndata: {\"id\": {token_idx}, \"text\": \"chunk_{token_idx} \"}\n\n"

except asyncio.CancelledError:
    logger.info("[Stream Cancelled] Task cancelled by ASGI server.")
    raise
except Exception as err:
    logger.error(f"[Stream Error] Error during streaming: {str(err)}")
    raise
finally:
    logger.info("[Stream Cleanup] Releasing context and triggering GC.")
    gc.collect()

@app.post("/api/v1/chat/stream")
async def chat_stream_endpoint(request: Request, body: dict):
prompt = body.get("prompt", "")
if not prompt:
raise HTTPException(status_code=400, detail="Prompt string is missing.")

return StreamingResponse(
    robust_llm_token_stream(prompt, request),
    media_type="text/event-stream",
    headers={
        "Cache-Control": "no-cache",
        "Connection": "keep-alive",
        "X-Accel-Buffering": "no"
    }
)

`

Fixation des dépendances et pipeline de build hors ligne

Il est fréquent qu'une simple mise à jour mineure en amont (upstream) casse la construction du conteneur local. Empêchez la falsification de paquets et les conflits de version grâce à un fichier requirements.txt intégrant des empreintes SHA-256.

`plaintext
fastapi==0.110.0 --hash=sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
uvicorn==0.28.0 --hash=sha256:2c6a81387e0037a34ca6a7f8087796030c79eb299f116515822ee483c6604e43
pydantic==2.6.4 --hash=sha256:d82e212f451f2d6c19f5a5e3a89369322e70e1781297587786411516279f64a5
sqlalchemy==2.0.28 --hash=sha256:a611116c2bb45f8f3077e6822ec3a37b384ff6b9a84d4dd88a0e8eb876b5cf12

`

Afin d'éviter les échecs de téléchargement via pip lors de la reconstruction de Docker dans un environnement hors ligne (réseau de sécurité d'entreprise ou en avion), pré-téléchargez les binaires Wheel dans un répertoire local.

`bash
pip wheel --wheel-dir=./wheels_repository -r requirements.txt

`

`dockerfile
FROM python:3.11-slim

WORKDIR /app

COPY ./wheels_repository /app/wheels_repository
COPY requirements.txt .

RUN pip install --no-cache-dir --no-index --find-links=/app/wheels_repository -r requirements.txt

COPY . .

CMD ["gunicorn", "-k", "uvicorn.workers.UvicornWorker", "-c", "gunicorn.conf.py", "app.main:app"]

`

Récupérer (pull) l'intégralité des modifications d'un dépôt Git en amont risque de balayer les configurations locales soigneusement ajustées. Isolez une branche d'optimisation et récupérez uniquement les commits nécessaires via un cherry-pick.

`bash
git remote add upstream https://github.com/opensource-ai-workspace/workspace.git
git fetch upstream
git checkout -b feature/local-mac-optimization
git cherry-pick

`

Contrôle des goulots d'étranglement des connexions DB et routage réseau isolé

Si les multi-agents effectuent des recherches par embedding, des consultations de mémoire et de la journalisation de conversation simultanément, le pool de connexions PostgreSQL s'épuisera en un instant. Sur la base d'un environnement à disque unique Apple Silicon avec 8 cœurs, limitez le pool du moteur à moins de 17 connexions en suivant la formule de calcul (Nextconn=extCPUcœursimes2+extnombredebrochesN_{ ext{conn}} = ext{CPU cœurs} imes 2 + ext{nombre de broches}Nextconn​=extCPUcœursimes2+extnombredebroches).

`python
from sqlalchemy.ext.asyncio import create_async_engine

DATABASE_URL = "postgresql+asyncpg://postgres:password@localhost:6432/ai_workspace"

engine = create_async_engine(
DATABASE_URL,
pool_size=15,
max_overflow=5,
pool_timeout=10,
pool_recycle=300,
pool_pre_ping=True
)

`

Le phénomène où un agent retient et prolonge une session de base de données en attendant les résultats d'inférence du LLM est coupé net grâce au pooling de transactions PgBouncer.

`ini
[databases]
ai_workspace = host=postgres_db port=5432 dbname=ai_workspace auth_user=postgres

[pgbouncer]
listen_addr = 0.0.0.0
listen_port = 6432
auth_type = plain
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = transaction
idle_transaction_timeout = 60
max_client_conn = 1000
default_pool_size = 20
min_pool_size = 5
reserve_pool_size = 5
reserve_pool_timeout = 3

`

`
+-------------------------------------------------------------------------------+
| Docker Internal Isolated Network (internal: true) |
| |
| +--------------------+ +--------------------+ +-----------------+ |
| | AI Workspace App | ---> | PgBouncer Proxy | ---> | PostgreSQL DB | |
| | (FastAPI Backend) | | (Port 6432) | | (Port 5432) | |
| +--------------------+ +--------------------+ +-----------------+ |
| | | |
| | [pool_mode = transaction] |
| | [idle_tx_timeout = 60s] |
| v |
| +--------------------+ |
| | Local Vector DB | (Communications Internet externes bloquées) |
| | (Qdrant) | |
| +--------------------+ |
+-------------------------------------------------------------------------------+

`

Pour éliminer tout risque de fuite de données, appliquez internal: true au réseau du conteneur afin de bloquer les communications cloud externes. La connexion au runtime Metal Ollama haute performance s'exécutant directement sur la machine hôte se fait uniquement via la passerelle host.docker.internal.

`yaml
version: '3.8'

services:
backend-app:
build: .
environment:
- DB_HOST=pgbouncer
- DB_PORT=6432
- VECTOR_DB_HOST=vector-db
- OLLAMA_HOST=http://host.docker.internal:11434
networks:
- isolated_local_net
extra_hosts:
- "host.docker.internal:host-gateway"
ports:
- "8000:8000"

pgbouncer:
image: edoburu/pgbouncer:latest
environment:
- DB_HOST=postgres_db
- DB_PORT=5432
- DB_USER=postgres
- DB_PASSWORD=secret
- POOL_MODE=transaction
networks:
- isolated_local_net
depends_on:
- postgres_db

postgres_db:
image: postgres:16-alpine
environment:
- POSTGRES_DB=ai_workspace
- POSTGRES_PASSWORD=secret
volumes:
- pgdata:/var/lib/postgresql/data
networks:
- isolated_local_net

vector-db:
image: qdrant/qdrant:v1.9.0
volumes:
- qdrant_data:/qdrant/storage
networks:
- isolated_local_net

etworks:
isolated_local_net:
driver: bridge
internal: true

volumes:
pgdata:
qdrant_data:

`

En contournant les goulots d'étranglement de la virtualisation GPU via Podman ou le pont hôte, et en contrôlant le cycle de vie grâce au recyclage des workers et au proxy de base de données, vous obtiendrez un environnement de développement indépendant qui ne plantera pas, même sur un ordinateur portable équipé d'une puce de la série M.