Auf dem Mac mini AI-Workloads planen: Swap vermeiden, wenn Ollama, Claude Code und GitHub Runner parallel laufen

L2-Q03 · Memory-Scheduling-Schicht

2026.06.04  ·  ~14 Min.  ·  Ops-Runbook, kein Installations-Tutorial

Unified Memory auf Mac mini: Ollama, Claude Code und GitHub Actions parallel — Scheduling statt Swap

Laufen auf Mac mini / Cloud Mac Ollama, Claude Code und GitHub Actions parallel, ist das häufigste Problem nicht „zu langsam“, sondern Swap, ruckelnde CLI und langsamere CI.

Dieser Text ohne Tool-Installation; sondern warum bei drei parallelen AI-Workloads der Speicher wackelt, und wie Scheduling das verhindert.Gegenbeispiel, Speicherbudget, Schwellen-Scheduling, 30-Sekunden-Runbook und vollständiges Skript; ohne Wiederholung des 16GB-vs.-24GB-Benchmarks.

TL;DR

  • Ollama dauerhaft + CI-burst → auf M4 häufig Swap (siehe Gegenbeispiel)
  • Meist kein Mac-Upgrade, sondern Schichtplan für burst / interactive / background
  • Vor CI ollama stop — wichtigster und einfachster Schritt (30-Sekunden-Version)

Das Modell AI workload scheduling: in Cloud-Mac-Szenarien kommen die meisten Swap/OOM eher aus Scheduling als aus absolut fehlendem RAM.

AI
Workload Scheduling
30s
Minimales Runbook
0
brew install

Das eigentliche Problem: meist nicht zu wenig RAM, sondern keine Prioritäten

Symptome: „lokales Modell im Leerlauf + CI-Spitze + träger Coding-Agent“ — oft fälschlich „mehr RAM kaufen“.

In unseren Cloud-Mac-Szenarien stammen die meisten Swap/OOM eher aus Workload-Scheduling als aus absolut fehlendem RAM. Auf einem Mac mini existieren drei Lasttypen (nächster Abschnitt):

  • BurstGitHub Runner / xcodebuild, bei push +4–8 GB
  • InteractiveClaude Code, IDE + Terminal + großes Repo
  • Background — lokale Inference wie Ollama; geladenes 8B hält still 5–7 GB

Standardmäßig konkurrieren alle gleichberechtigt um Unified Memory — niemand „muss weichen“.Die Richtung: Prioritäten und Trigger definieren, Background-Inference von „nach Install dauerhaft“ zu entladbarer, von CI verdrängbarer Ressource machen.

Schlechtes Scheduling (auf M4 häufig, kein Einzelfall)

Kombination, die wir auf Cloud Mac in kleinen Teams wirklich gesehen haben — als „verbotenes Muster“ beim Onboarding:

Bad pattern · all three workloads default「full on」

  qwen3:8b  always loaded          →  5–7 GB (background, zero calls)
  push triggers xcodebuild           →  +4–8 GB peak (burst)
  Claude Code indexing large repo     →  +2–3 GB (interactive)

Observed (24GB M4 Mac mini)
  Swap  0 → 2.1 GB
  xcodebuild link stage latency  +~40%
  Claude Code terminal noticeably sluggish

Auf M4 mit Unified Memory ist dauerhaftes 8B + CI-burst + interaktives Coding parallel sehr oft Swap.Der Wert des Gegenbeispiels: workload scheduling ist kein Nice-to-have, sondern Voraussetzung bei mehreren Lasten.

Drei Lastformen: burst / interactive / background

Scheduling nur nach der Uhr reicht nicht. Auf Cloud Mac unterscheiden sich die Speicherprofile stark:

Form Beispiel Schicht Speicherprofil Priorität
Burst xcodebuild, Link, Simulator L1 Runner Spitze, unvorhersehbar Höchste — Fact darf nicht scheitern
Interactive Claude Code, IDE, SSH L3 mittel, Mensch am Gerät Hoch — Inference über API, kein Großmodell
Background Ollama Embedding, Log-Zusammenfassung L2 verzögerbar, entladbar Niedrigste — muss burst weichen

Kurz: L2 ist die einzige Schicht im Stack, die „vom Host geworfen“ werden darf — nicht weil sie unwichtig ist, sondern weil ihre Tasks meist asynchron und wiederholbar sind.

Speicherbudget: Anteil der drei Workloads

Ollama / Claude Code / GitHub Runner als Beispiel-Komponenten; Zahlen ausM4 Mac mini Messungen(Steady State, keine Compile-Spitzen):

Komponente Schicht Typisch Spitzen
macOS + System-Cache L0 3–4 GB relativ stabil
Claude Code Workspace L3 1–3 GB Inference über API, keine Modellgewichte
GitHub Runner job L1 2–6 GB (steady) Link-Phase +4–8 GB
Ollama · qwen3:8b L2 5–7 GB freigegeben mit ollama stop
Ollama · qwen3:14b L2 9–13 GB mit burst leicht 2GB+ Swap
Ollama · nomic-embed-text L2 0.3–0.8 GB leichtes background tagsüber dauerhaft

Grobrechnung 24GB tagsüber „Coding + CI + 8B dauerhaft“ ≈ 17 GB — noch Puffer; Mit dauerhaftem 14B schnell über 22 GB.Die Budget-Tabelle beantwortet „geht es?“; Scheduling beantwortet „wer wann Speicher nutzt“.

Scheduling-Modell: von Zeitfenstern zu memory_pressure-Schwellen

Einstieg mit Tag/Nacht-Tabelle (Modus ② unten); operativ reifer: Scheduling nach Speicherdruck — ohne dass jemand „22 Uhr Batch“ erinnern muss:

AI Workload Scheduler · L2-Q03 recommended rules

When memory_pressure enters warn / critical (or equivalent > ~70%):
  → auto ollama stop qwen3:8b / qwen3:14b

When memory_pressure normal (< ~50%) and idle > 10 min:
  → auto preload nomic-embed-text (keepalive 10m)

When CI event trigger (Runner job start):
  → force CI mode: stop all Ollama large models, priority L1 > L3 > L2

When CI job succeeds + memory drops:
  → async trigger L2 batch (log summary / embedding rebuild)

Zeitfenster sind Baseline; Schwellen-Scheduling ist Upgrade. Kleine Teams starten mit Runbook + stop vor CI; danach memory_pressure-Guard (§ Runbook).

Drei Basis-Scheduling-Modi

Grundstrategien zusammen mit Schwellen-Scheduling:

Modus Vorgehen Passt für
① Leicht parallel tagsüber nur nomic-embed-text; 8B/14B on-demand 16GB Mac mini, Fokus Coding
② Zeitfenster 09–18 Coding+CI / 22–06 Nacht-Batch 24GB, geplante Embedding-/Log-Tasks
③ CI löst Yield aus vor Job ollama stop, danach asynchron L2 häufige pushes, hohe xcodebuild-Spitzen

Empfohlen: ① + ③ für alle; ② als Nacht-Batch-Ergänzung; Schwellen-Guard als Netz.

Pipeline-Aufteilung: was auf L2, was auf L3

Vor dem Scheduling die Pipeline festlegen, sonst läuft Ollama leer (siehe L2-Q01 · typische Fehleinschätzung):

Aufgabe Schicht Scheduling-Hinweis
Repo ändern, Patch erzeugen L3 Claude Code(API) kein lokales Großmodell
Build, Test, Archiv L1 Runner höchste burst-Priorität; L2 vor Spitze stoppen
CI-Fehlerlog-Zusammenfassung L2 qwen3:8b nach Job asynchron oder Nacht-Batch
CodeGraph / RAG embedding L2 nomic-embed-text tagsüber dauerhaft möglich (<1GB)

Runner-Spitzen und CI-Versatz

L1 Runner liefert Fact — Inference ersetzt kein Build-Ergebnis. Minimale CI-Änderung:

# .github/workflows/ios.yml · self-hosted macOS runner
- name: Enter CI mode — free memory for xcodebuild
  run: |
    ollama ps
    ollama stop qwen3:8b 2>/dev/null || true
    ollama stop qwen3:14b 2>/dev/null || true
    sleep 30   # wait for memory reclaim; do not start xcodebuild same second

- name: Build
  run: xcodebuild ...

Gemessen:Auf 24GB M4 gibt ollama stop qwen3:8b freigibt in ~5–15 s 5–7 GB; bei bestehendem Swap dauert vollständige Rückgewinnung Minuten — Daher muss stop vor CI mindestens 30 Sekunden früher erfolgen.

Runbook: 30-Sekunden-Version und vollständiges Skript

30-Sekunden-Version (für die meisten reicht das)

Keine Zeit für das volle Skript? Diese drei Blöcke decken ~80 % ab:

① Vor CI (entscheidend) — in GitHub Actions oder Runner-Hook:

ollama stop qwen3:8b
ollama stop qwen3:14b
sleep 30

② Tagsüber — nur leichtes Embedding, keine großen Modellgewichte:

ollama run nomic-embed-text --keepalive 30m

③ Nacht — im Batch-Fenster 8B laden:

ollama run qwen3:8b
# then run your log summary / embedding rebuild scripts

Vollversion (Produktions-Runbook)

Für LaunchAgent / cron / mehrere Umgebungen speichern als ~/bin/cloud-mac-stack-runbook.sh

Vollständiges Runbook · Unterbefehle

day-start · ci-pre · ci-post · night-batch

#!/usr/bin/env bash
# cloud-mac-stack-runbook.sh — L2-Q03 standard Runbook
set -euo pipefail

OLLAMA_HOST="${OLLAMA_HOST:-127.0.0.1:11434}"
export OLLAMA_MAX_LOADED_MODELS=1

ensure_ollama() {
  curl -sf "http://${OLLAMA_HOST}/api/tags" >/dev/null || ollama serve &
  sleep 2
}

ci_pre() {
  # before CI: force CI mode, L1 wins
  ollama ps || true
  ollama stop qwen3:8b 2>/dev/null || true
  ollama stop qwen3:14b 2>/dev/null || true
  sleep 30
}

ci_post() {
  # after CI: restore light embedding (lowest background tier)
  ensure_ollama
  ollama run nomic-embed-text --keepalive 10m
}

day_start() {
  # login / boot: embedding only or stop all
  ensure_ollama
  ollama stop qwen3:8b 2>/dev/null || true
  ollama stop qwen3:14b 2>/dev/null || true
  ollama run nomic-embed-text --keepalive 30m
}

night_batch() {
  # night batch (cron: 0 22 * * *)
  ensure_ollama
  ollama run qwen3:8b --keepalive 6h
  # ./your-log-summary-or-embed-rebuild.sh
}

case "${1:-}" in
  day-start)   day_start ;;
  ci-pre)      ci_pre ;;
  ci-post)     ci_post ;;
  night-batch) night_batch ;;
  *) echo "Usage: $0 {day-start|ci-pre|ci-post|night-batch}"; exit 1 ;;
esac

Memory guard (cron alle 5 Minuten oder LaunchAgent empfohlen): bei hohem Speicherdruck große Modelle entladen — minimale Umsetzung von Schwellen-Scheduling.

#!/usr/bin/env bash
# memory-guard.sh — memory_pressure threshold guard
PRESSURE=$(memory_pressure 2>/dev/null | head -1 || true)

if echo "$PRESSURE" | grep -qiE 'warn|critical|urgent'; then
  logger -t cloud-mac-stack "memory guard: stopping Ollama 8B/14B ($PRESSURE)"
  ollama stop qwen3:8b 2>/dev/null || true
  ollama stop qwen3:14b 2>/dev/null || true
fi

# optional: when pressure normal + no Runner job, restore embedding
# if echo "$PRESSURE" | grep -qi 'normal'; then ... fi

Anbindung (Beispiele):

  • GitHub Actions — erster Step ci-pre, letzter ci-post
  • LaunchAgent — bei Login day-start; 22:00 cron night-batch
  • cron*/5 * * * * /path/memory-guard.sh

16GB Mac mini planen

  • 14B nicht dauerhaft; 8B nur in night-batch
  • Tagsüber nur day-start (Embedding oder alles stoppen)
  • Jede CI braucht ci-pre, ohne Ausnahme
  • Wenn „Desktop + 8B + Claude Code“ dauerhaft parallel laufen soll → 24GB; Scheduling hebt keine Hardware-Grenze

16GB: tagsüber kein großes Modell, nachts ein Modell pro Task, vor CI immer ci-pre.24GB: tagsüber Embedding, 8B gegen CI versetzt, 14B nur nachts.

Entscheidungstabelle: welche Strategie

Ihre Situation Empfehlung
24GB · wenige pushes · vor allem Claude Code day-start + Embedding; kein Nacht-Batch
24GB · tägliche CI · Log-Zusammenfassung ci-pre/post + night-batch + Memory Guard
16GB · lokales 8B nötig nur night-batch; tagsüber Claude API
Ollama eine Woche ohne Aufruf zuerst L2-Q01 — Pipeline definieren

Serie · Cloud Mac AI Stack

L2-Q03 · Memory-Scheduling-Schicht — beantwortet nach außen „Swap auf Mac mini vermeiden“; intern Fortsetzung von L2-Q01 Private Inference-Schicht:

  • L2-Q01 — Was Inference ist (Positionierung)
  • L2-Q03 · dieser Text — Memory-Scheduling-Schicht (Scheduling auf einem Host)
  • Geplant — Model-Pinning, Health-Check 11434, Ollama-Aufruf aus CI
  • Downstream — L4 Context + MCP-Scheduling (morgen L4-Q03)

Kein Ollama-Tutorial und kein reiner CI/CD-Tuning-Text — sondern erste Schicht eines AI-Workload-Schedulers auf Apple Silicon.

FAQ

Führen Ollama, Claude Code und GitHub Actions parallel auf dem Mac mini zu Swap?
Ja, wenn burst-, interactive- und background-Workloads ohne Priorität parallel laufen. Siehe § Das eigentliche Problem und Gegenbeispiel.

Muss Ollama dauerhaft laufen?
Nein. Behandeln Sie lokale Inference als schedulbare Ressource: tagsüber nur nomic-embed-text, große Modelle on-demand oder im Nacht-Batch.

Ollama vor CI-Builds stoppen?
Empfohlen. Siehe 30-Sekunden-Runbook · vor CI oder ci-pre in der Vollversion.

Claude Code und Ollama parallel?
Ja. Der Coding-Pfad nutzt die API; lokal konkurrieren Modellgewichte und CI-Spitzen. CI-vor-stop oder Zeitfenster reichen.

Ist OOM auf Cloud Mac ein RAM-Problem?
In unseren Szenarien stammen die meisten OOM eher aus Scheduling als aus absolut fehlendem RAM. Die 16GB-Grenze: L2-Q02 Messungen.

Zeitfenster oder memory_pressure-Schwellen?
Zuerst das 30-Sekunden-Runbook; danach memory-guard.sh (siehe vollständiges Runbook unten).

Worin unterscheidet sich dieser Text von L2-Q01?
Q01 klärt die Inference-Service-Position; Q03 (Memory-Scheduling-Schicht) erklärt, wie AI-Workloads auf derselben Maschine geplant werden.

Cloud Mac AI Stack · Vorschau morgen

Claude Code + MCP: GitHub, lokale Dateien und API in einer Kette

Wenn L2-Schichten stehen, folgt L4 Context: wie MCP GitHub, Repo-Dateien und APIs in den Claude-Code-Workflow einbindet.

Zum Blog · Cloud Mac AI Stack
Workload-Planung Cloud Mac Preise