Локальные LLM на Mac
В десктопном приложении Hermes раздел Settings → Providers → Local Models сам поставит и настроит локальный сервер llama.cpp: скачает модели, подберёт раскладку по памяти и размер контекста. Подробности — в разделе Локальные модели. Это же руководство — про ручную настройку: MLX, самосборные бинарники или сервер, который вы хотите держать под своим контролем.
Это руководство проведёт вас через настройку локального LLM-сервера на macOS с OpenAI-совместимым API. Полная приватность, нулевые расходы на API и неожиданно хорошая скорость на Apple Silicon.
Разберём два бэкенда:
| Бэкенд | Установка | Сильные стороны | Формат |
|---|---|---|---|
| llama.cpp | brew install llama.cpp | Минимальное время до первого токена, квантованный KV-кэш для экономии памяти | GGUF |
| omlx | omlx.ai | Максимальная скорость генерации, нативная оптимизация Metal | MLX (safetensors) |
Оба бэкенда предоставляют OpenAI-совместимый эндпоинт /v1/chat/completions. Hermes работает с любым из них — достаточно указать http://localhost:8080 или http://localhost:8000.
Руководство рассчитано на Mac с Apple Silicon (M1 и новее). На Intel Mac llama.cpp работает, но без GPU-ускорения — скорость будет заметно ниже.
Выбор модели
Для старта рекомендуем Qwen3.5-9B — сильная модель с хорошим умозаключением, которая комфортно помещается в 8+ ГБ общей памяти при использовании квантования.
| Вариант | Размер на диске | Нужно RAM (контекст 128K) | Бэкенд |
|---|---|---|---|
| Qwen3.5-9B-Q4_K_M (GGUF) | 5,3 ГБ | ~10 ГБ с квантованным KV-кэшем | llama.cpp |
| Qwen3.5-9B-mlx-lm-mxfp4 (MLX) | ~5 ГБ | ~12 ГБ | omlx |
Приблизительный расчёт памяти: размер модели + KV-кэш. Модель 9B Q4 весит ~5 ГБ. KV-кэш при контексте 128K с квантованием Q4 добавляет ещё ~4–5 ГБ. При KV-кэше по умолчанию (f16) это разрастается до ~16 ГБ. Флаги квантованного KV-кэша в llama.cpp — главный способ экономить память на ограниченных машинах.
Для больших моделей (27B, 35B) нужно 32+ ГБ общей памяти. Модель 9B — оптимальный выбор для машин с 8–16 ГБ.
Вариант A: llama.cpp
llama.cpp — самый переносимый локальный LLM-рантайм. На macOS он использует Metal для GPU-ускорения прямо из коробки.
Установка
brew install llama.cpp
После этого команда llama-server доступна глобально.
Загрузка модели
Нужна модель в формате GGUF. Удобнее всего скачивать с Hugging Face через huggingface-cli:
brew install huggingface-cli
Затем скачиваем:
huggingface-cli download unsloth/Qwen3.5-9B-GGUF Qwen3.5-9B-Q4_K_M.gguf --local-dir ~/models
Некоторые модели на Hugging Face требуют аутентификации. Если получаете ошибку 401 или 404 — сначала выполните huggingface-cli login.
Запуск сервера
llama-server -m ~/models/Qwen3.5-9B-Q4_K_M.gguf \
-ngl 99 \
-c 131072 \
-np 1 \
-fa on \
--cache-type-k q4_0 \
--cache-type-v q4_0 \
--host 0.0.0.0
Что делает каждый флаг:
| Флаг | Назначение |
|---|---|
-ngl 99 | Выгрузить все слои на GPU (Metal). Большое значение гарантирует, что ничего не останется на CPU. |
-c 131072 | Размер контекстного окна (128K токенов). Уменьшите, если не хватает памяти. |
-np 1 | Количество параллельных слотов. Для одного пользователя — 1: больше слотов делят бюджет памяти. |
-fa on | Flash attention. Снижает расход памяти и ускоряет вывод на длинных контекстах. |
--cache-type-k q4_0 | Квантование ключевого кэша до 4 бит. Главный способ экономить память. |
--cache-type-v q4_0 | Квантование кэша значений до 4 бит. Вместе с предыдущим флагом сокращает KV-кэш на ~75% по сравнению с f16. |
--host 0.0.0.0 | Слушать на всех интерфейсах. Используйте 127.0.0.1, если сетевой доступ не нужен. |
Сервер готов, когда вы увидите:
main: server is listening on http://0.0.0.0:8080
srv update_slots: all slots are idle
Оптимизация памяти на ограниченных системах
Флаги --cache-type-k q4_0 --cache-type-v q4_0 — самая важная оптимизация для машин с ограниченной памятью. Вот разница на контексте 128K:
| Тип KV-кэша | Расход памяти (128K контекст, модель 9B) |
|---|---|
| f16 (по умолчанию) | ~16 ГБ |
| q8_0 | ~8 ГБ |
| q4_0 | ~4 ГБ |
На Mac с 8 ГБ используйте KV-кэш q4_0 и выбирайте модель поменьше, которая всё ещё укладывается в минимальный контекст Hermes (64K). На 16 ГБ контекст 128K работает комфортно. На 32+ ГБ можно запускать большие модели или несколько параллельных слотов.
Если память всё равно заканчивается — уменьшайте контекст, но не ниже минимума Hermes в 64K. Либо переходите на модель меньшего размера или меньшее квантование (Q3_K_M вместо Q4_K_M).
Проверка
curl -s http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "Qwen3.5-9B-Q4_K_M.gguf",
"messages": [{"role": "user", "content": "Hello!"}],
"max_tokens": 50
}' | jq .choices[0].message.content
Узнать имя модели
Если забыли имя модели — запросите эндпоинт моделей:
curl -s http://localhost:8080/v1/models | jq '.data[].id'
Вариант B: MLX через omlx
omlx — нативное macOS-приложение для управления и раздачи MLX-моделей. MLX — собственный фреймворк Apple для машинного обучения, оптимизированный под архитектуру общей памяти Apple Silicon.
Установка
Скачайте и установите с omlx.ai. Приложение предоставляет GUI для управления моделями и встроенный сервер.
Загрузка модели
Просматривайте и скачивайте модели прямо в интерфейсе omlx. Найдите Qwen3.5-9B-mlx-lm-mxfp4 и загрузите её. Модели хранятся локально (обычно в ~/.omlx/models/).
Запуск сервера
По умолчанию omlx раздаёт модели на http://127.0.0.1:8000. Запустите раздачу через UI приложения или через CLI, если он доступен.
Проверка
curl -s http://127.0.0.1:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "Qwen3.5-9B-mlx-lm-mxfp4",
"messages": [{"role": "user", "content": "Hello!"}],
"max_tokens": 50
}' | jq .choices[0].message.content
Список доступных моделей
omlx может раздавать несколько моделей одновременно:
curl -s http://127.0.0.1:8000/v1/models | jq '.data[].id'
Бенчмарки: llama.cpp против MLX
Оба бэкенда тестировались на одной машине (Apple M5 Max, 128 ГБ общей памяти) с одинаковой моделью (Qwen3.5-9B) при сопоставимом квантовании (Q4_K_M для GGUF, mxfp4 для MLX). Пять разнообразных промптов, по три запуска каждый, бэкенды тестировались поочерёдно во избежание конкуренции за ресурсы.
Результаты
| Метрика | llama.cpp (Q4_K_M) | MLX (mxfp4) | Победитель |
|---|---|---|---|
| TTFT (среднее) | 67 мс | 289 мс | llama.cpp (в 4,3 раза быстрее) |
| TTFT (p50) | 66 мс | 286 мс | llama.cpp (в 4,3 раза быстрее) |
| Генерация (среднее) | 70 tok/s | 96 tok/s | MLX (на 37% быстрее) |
| Генерация (p50) | 70 tok/s | 96 tok/s | MLX (на 37% быстрее) |
| Полное время (512 токенов) | 7,3 с | 5,5 с | MLX (на 25% быстрее) |
Что это означает на практике
-
llama.cpp выигрывает при обработке промпта — связка flash attention + квантованный KV-кэш даёт первый токен уже через ~66 мс. Для интерактивных приложений, где важна воспринимаемая отзывчивость (чат-боты, автодополнение), это ощутимое преимущество.
-
MLX генерирует токены на ~37% быстрее после первого. Для пакетных задач, длинного контента или любой работы, где важно общее время выполнения, MLX финиширует раньше.
-
Оба бэкенда показывают очень стабильные результаты — разброс между запусками минимален. На эти цифры можно опираться.
Что выбрать?
| Сценарий | Рекомендация |
|---|---|
| Интерактивный чат, низкая задержка | llama.cpp |
| Длинная генерация, пакетная обработка | MLX (omlx) |
| Ограниченная память (8–16 ГБ) | llama.cpp (квантованный KV-кэш вне конкуренции) |
| Несколько моделей одновременно | omlx (встроенная поддержка) |
| Максимальная совместимость (в том числе Linux) | llama.cpp |
Подключение к Hermes
После запуска локального сервера:
hermes model
Выберите Custom endpoint и следуйте подсказкам. Нужно будет указать базовый URL и имя модели — возьмите значения из настроенного бэкенда.
Таймауты
Hermes автоматически распознаёт локальные эндпоинты (localhost, LAN-адреса) и увеличивает таймауты стриминга. Для большинства сетапов никакой настройки не нужно.
Если таймауты всё равно срабатывают (например, очень большие контексты на медленном железе), можно переопределить таймаут чтения стрима:
# В .env — увеличиваем с 120 с по умолчанию до 30 минут
HERMES_STREAM_READ_TIMEOUT=1800
| Таймаут | По умолчанию | Авто-корректировка для локальных | Переменная окружения |
|---|---|---|---|
| Чтение стрима (на уровне сокета) | 120 с | Повышается до 1800 с | HERMES_STREAM_READ_TIMEOUT |
| Обнаружение зависшего стрима | 180 с | Полностью отключается | HERMES_STREAM_STALE_TIMEOUT |
| API-вызов (без стриминга) | 1800 с | Изменений не нужно | HERMES_API_TIMEOUT |
Чаще всего проблемы возникают именно с таймаутом чтения стрима — это дедлайн на уровне сокета для получения очередного чанка данных. При prefill на больших контекстах локальная модель может не выдавать ни байта несколько минут, пока обрабатывает промпт. Авто-определение локальных эндпоинтов решает это прозрачно.
Hermes при каждом вызове отправляет системный промпт и схемы инструментов, поэтому на медленном железе первый ход может сопровождаться несколькими минутами тишины, пока модель обрабатывает этот промпт, прежде чем начать генерацию. Это работа prefill, а не зависшая сессия. Способы смягчить эффект — держать модель загруженной и сократить фиксированный промпт через hermes prompt-size — см. в разделе Медленный первый ответ (prefill) руководства по Ollama.