На Mac mini — планирование AI-нагрузок: как избежать swap при Ollama, Claude Code и GitHub Runner

L2-Q03 · Слой планирования памяти

2026.06.04  ·  ~14 мин  ·  ops-runbook, без туториала по установке

Unified Memory на Mac mini: Ollama, Claude Code и GitHub Actions — scheduling вместо swap

На Mac mini / Cloud Mac при параллельном запуске Ollama, Claude Code и GitHub Actions чаще всего проблема не «мало производительности», а swap, рывки CLI и замедление CI.

В этой статье без установки инструментов: почему при трёх параллельных AI-workload память «дрожит» и как scheduling это предотвращает. Антипример, бюджет памяти, пороги, runbook на 30 с и полный скрипт — без повтора бенчмарка 16GB vs 24GB.

TL;DR

  • Постоянный Ollama + burst CI → на M4 swap очень част (см. антипример)
  • Решение обычно не в апгрейде Mac, а план слоёв для burst / interactive / background
  • Перед CI ollama stop — самый важный и простой шаг (версия на 30 с)

Модель AI workload scheduling: на Cloud Mac большинство swap/OOM — из-за scheduling, а не абсолютного дефицита RAM.

AI
Workload Scheduling
30s
Минимальный runbook
0
строк brew install

Настоящая проблема: чаще не нехватка RAM, а отсутствие приоритетов

Симптомы: «пустой локальный модель + пик CI + тормозит агент» — часто ошибочно «купить RAM».

В сценариях Cloud Mac большинство swap/OOM — из-за scheduling workload, а не абсолютного дефицита RAM. На одном Mac mini три типа нагрузки (см. ниже):

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

По умолчанию все равноправно делят unified memory — никто «не обязан уступать».Направление: задать приоритеты и триггеры, сделать background-inference снимаемым и вытесняемым CI.

Плохое планирование (на M4 часто, не редкость)

Комбинация, которую мы видели на Cloud Mac в малых командах — «запретный режим» при 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

На M4 с unified memory постоянный 8B + burst CI + интерактивное кодирование = частый swap.Смысл антипримера: scheduling — не опция, а условие при нескольких нагрузках.

Drei Lastformen: burst / interactive / background

Планировать только по часам нельзя. На Cloud Mac профили памяти разные:

Форма Пример Слой Профиль памяти Приоритет
Burst xcodebuild, Link, Simulator L1 Runner пик, непредсказуемо Наивысший — Fact не должен падать
Interactive Claude Code, IDE, SSH L3 средний, человек у машины высокий — inference через API, без большой модели
Background Ollama Embedding, Log-Zusammenfassung L2 можно отложить, выгрузить Niedrigste — muss burst weichen

Кратко: L2 — единственный слой, который можно «выкинуть» — задачи в основном асинхронны и повторяемы.

Бюджет памяти · доли трёх workload

Ollama / Claude Code / GitHub Runner как пример компонентов; цифры иззамеров Mac mini M4(steady state, не пик компиляции):

Компонент Слой Типично Пики
macOS + системный кэш L0 3–4 GB относительно стабильно
Рабочая область Claude Code L3 1–3 GB inference через API, без весов модели
GitHub Runner job L1 2–6 GB (steady) фаза линковки +4–8 GB
Ollama · qwen3:8b L2 5–7 GB освобождается через ollama stop
Ollama · qwen3:14b L2 9–13 GB с burst легко swap 2GB+
Ollama · nomic-embed-text L2 0.3–0.8 GB лёгкий background днём

Грубо: 24GB днём «код + CI + 8B постоянно» ≈ 17 GB — ещё запас; с постоянным 14B легко >22 GB.Die Budget-Tabelle beantwortet „geht es?“; Scheduling beantwortet „wer wann Speicher nutzt“.

Модель scheduling: от слотов к порогам memory_pressure

Старт с таблицы день/ночь (режим ②); зрелее — пороги давления памяти — без «в 22:00 batch» в голове:

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)

Слоты — baseline; пороговый scheduling — upgrade. Малой команде: runbook + stop перед CI; затем memory_pressure-guard (§ Runbook).

Три базовых режима

Базовые стратегии с порогами:

Режим Как Подходит
① Лёгкий параллелизм tagsüber nur nomic-embed-text; 8B/14B on-demand Mac mini 16GB, упор на код
② Разделение по времени 09–18 код+CI / 22–06 ночной batch 24GB, плановый embedding / логи
③ CI вытесняет перед job ollama stop, затем асинхронно L2 частые push, высокие пики xcodebuild

Рекомендуем: ① + ③ для всех; ② как ночной batch; guard порогов как сеть.

Разделение pipeline · L2 vs L3

Сначала зафиксировать pipeline, иначе Ollama пустует (см. L2-Q01 · типичная ошибка):

Задача Слой Заметка scheduling
правки в репо, patch L3 Claude Code(API) без локальной большой модели
сборка, тест, архив L1 Runner макс. приоритет burst; stop L2 до пика
сводка логов при падении CI L2 qwen3:8b после job или ночной batch
CodeGraph / RAG embedding L2 nomic-embed-text можно днём (<1GB)

Пики Runner и разнос CI

L1 Runner даёт Fact — inference не заменяет build. Минимум в CI:

# .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 освобождает 5–7 GB за ~5–15 с; при swap полный откат — минуты — поэтому stop перед CI минимум за 30 с.

Runbook: версия на 30 с und vollständiges Skript

Версия 30 с (хватит большинству)

Keine Zeit для das volle Skript? Diese drei Blöcke decken ~80 % ab:

① Перед 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

Полная версия (прод)

Для LaunchAgent / cron / нескольких сред сохранить как ~/bin/cloud-mac-stack-runbook.sh

Полный runbook · подкоманды

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 каждые 5 мин или LaunchAgent): выгрузка больших моделей при давлении — минимум порогового 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

Примеры подключения:

  • GitHub Actions — erster Step ci-pre, letzter ci-post
  • LaunchAgent — при входе day-start; cron 22:00 night-batch
  • cron*/5 * * * * /path/memory-guard.sh

План для Mac mini 16GB

  • постоянный 14B запрещён; 8B только в night-batch
  • Днём только day-start (embedding или полная остановка)
  • Jede CI braucht ci-pre, ohne Ausnahme
  • Wenn „Desktop + 8B + Claude Code“ dauerhaft parallel laufen soll → 24GB; Scheduling hebt keine Hardware-Grenze

16GB: днём без большой модели, ночью одна модель — одна задача, перед CI всегда ci-pre. 24GB: днём embedding, 8B вразнос с CI, 14B только ночью.

Какую стратегию выбрать

Ваш случай Рекомендация
24GB · мало push · в основном 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 неделю без вызовов zuerst L2-Q01 — Pipeline definieren

Serie · Cloud Mac AI Stack

L2-Q03 · Слой планирования памяти — снаружи «как избежать swap на Mac mini»; внутри продолжение L2-Q01 приватного inference:

  • L2-Q01 — что такое Inference (позиция)
  • L2-Q03 · эта статья — слой планирования памяти (scheduling на одном хосте)
  • 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 — а erste Слой eines AI-Workload-Schedulers auf Apple Silicon.

FAQ

Ollama, Claude Code и GitHub Actions параллельно на Mac mini — будет swap?
Да, если burst-, interactive- и background-workload без приоритетов. См. § Настоящая проблема и антипример.

Ollama должен работать постоянно?
Нет. Локальный inference — планируемый ресурс: днём только nomic-embed-text, большие модели по запросу или ночным batch.

Останавливать Ollama перед CI?
Рекомендуется. См. runbook 30 с · перед CI или ci-pre в полной версии.

Claude Code и Ollama вместе?
Да. Кодинг через API; локально — веса и пики CI. stop перед CI или слоты.

OOM на Cloud Mac — нехватка RAM?
В наших сценариях большинство OOM — из-за scheduling, не абсолютный дефицит RAM. Жёсткая граница 16GB: замеры L2-Q02.

Слоты или пороги memory_pressure?
Сначала runbook на 30 с; затем memory-guard.sh (см. полный runbook ниже).

Чем эта статья отличается от L2-Q01?
Q01 — позиция Inference Service; Q03 (слой планирования памяти) — как планировать AI-workload на одном хосте.

Cloud Mac AI Stack · анонс завтра

Claude Code + MCP: GitHub, локальные файлы и API в одной цепочке

Когда слои L2 зафиксированы, следующий шаг — L4 Context: как MCP связывает GitHub, файлы репозитория и API с Claude Code.

К блогу · Cloud Mac AI Stack
Планирование AI Тарифы Cloud Mac