Локальные модели для приватности: Ollama на своём VPS

Облачная языковая модель — быстрый старт, но у него три цены: данные уходят за пределы сервера, оплата за каждый токен и зависимость от чужого аптайма. Для части задач — черновик маркетингового текста — это нормально. Для других — анализ логов с токенами доступа, разбор клиентских документов, приватный чат-бот на внутреннем сайте — нет: туда облако вести нельзя, и дело не только в GDPR, а в том, что, отправив текст в облако, вы больше не контролируете его копию.

Один из выходов — запускать модель на своей машине. Самый простой инструмент для этого на сегодня — Ollama: одна бинарная служба, которая скачивает модель и поднимает локальный API, совместимый с OpenAI. В этой статье — как поставить Ollama на VPS, что выбрать по размеру и языку (точные веса моделей сверены с официальной библиотекой на дату публикации), сколько памяти это ест и как воткнуть в PHP-код вместо облачного вызова.

Это четвёртая статья серии про ИИ-инструменты для контента и SEO. До этого разбирали форму запроса (Промпт-инжиниринг), массовую генерацию (Генерация мета-тегов) и retrieval по своему контенту (RAG на SQLite). RAG-контур из той статьи остаётся прежним — меняется только то, где живёт модель в call_model.

Часть I. Когда облако не подходит

Не всякую задачу надо тащить в локаль. Облачный API выигрывает, когда нужен максимум качества на редких сложных запросах, а данные нечувствительны. Но есть четыре сигнала, что стоит задуматься о своей модели:

  • Приватность данных. Токены, персональные данные клиентов, коммерческая переписка, аналитика логов — всё, что нельзя копировать на чужой сервер. Юридически (152-ФЗ, GDPR, отраслевые требования) и практически: отправив текст в облако, вы теряете контроль над его копией.
  • Цена на потоке. Чат-бот с высокой посещаемостью или автоматический разбор тысяч документов оплачивается за каждый миллион токенов. На определённом объёме своё железо окупается, и облако начинает стоить дороже.
  • Автономность и офлайн. Сайт обязан работать, когда у вендора АPI сбой или канал до него лёг. Локальная модель живёт на том же VPS, что и остальной сервер.
  • Латентность. Облачный запрос идёт через сеть; локальный — только через localhost. Для интерактива это может быть решающим.

Важная честная рамка: своя модель не бесплатна. Она стоит RAM и CPU/GPU вашего сервера и места на диске, и она слабее облачного флагмана по качеству. Локаль — это компромисс «приватность/контроль в обмен на мощность», и к нему стоит переходить осознанно, а не «потому что модно».

Часть II. Ollama на своём VPS: от установки до первого ответа

Ollama распространяется одним скриптом и работает как обычная служба. Установка на Linux-сервер:

Bash
curl -fsSL https://ollama.com/install.sh | sh

После установки Ollama поднимается как systemd-юнит ollama.service и слушает порт 11434 на localhost. Первый запуск модели (скачивание весов и ответ) — одной командой:

Bash
ollama run qwen2.5:7b

Настройка доступа и юнита

Два решения после установки. Первое: по умолчанию служба слушает только 127.0.0.1 — это правильно, модель не должна торчать наружу. Если веб-код на том же сервере — оставляйте localhost; пробрасывать 11434 в интернет без аутентификации — дыра (об этом ниже, в граблях). Второе — как сервис переживает перезагрузку. Это ровно те же понятия, что разобраны на примере обычных сервисов: systemd: как сервис живёт на сервере. Типичный drop-in для автозапуска и лимита памяти:

# /etc/systemd/system/ollama.service.d/override.conf
[Service]
# старт после сети, рестарт при падении — базовая гигиена юнита
Restart=on-failure
# если сервер общий, ограничьте потребление, чтобы модель не «съела» PHP-FPM
MemoryMax=8G
systemctl daemon-reload && systemctl enable --now ollama
# проверить, что сервис поднялся и слушает:
systemctl status ollama --no-pager
ss -ltnp | grep 11434

Дальше всё управление моделями — через CLI: ollama list (что скачано), ollama pull <tag> (забрать версию), ollama rm <tag> (освободить диск). Никакой отдельной инфраструктуры: один процесс, один порт, папка с весами.

Часть III. Что выбрать по размеру и языку

Точные размеры моделей ниже взяты с официальной страницы библиотеки Ollama на дату подготовки статьи; линейка и кванты обновляются — перед pull сверяйте актуальные теги по адресу https://ollama.com/library.

Для VPS общего назначения рабочий диапазон — 7–8 миллиардов параметров, q4_K_M квантования. Это тот размер, который влезает в умеренную RAM и при этом связно говорит и пишет код. Что брать по задаче:

МодельТег по умолчаниюРазмер (q4_K_M)КонтекстЧем хороша
Qwen2.5qwen2.5:7b (он же latest)4.7 GB32Kмногоязычная, хорошо держит русский; размерность-многопрофильная
Llama 3.1llama3.1:8b4.9 GB128Kсильный английский и код; большой контекст
Mistral (v0.3)mistral:7b4.4 GB32Kкомпактная, быстрая, «рабочая лошадка»

Цифры — не маркетинговые «параметры», а фактический размер веса на диске (сверено: qwen2.5:7b=4.7 GB, llama3.1:8b=4.9 GB, mistral:7b=4.4 GB). Разница в контексте заметна сразу: Llama 3.1 на 128K помещает в окно за раз целый длинный документ, а Mistral на 32K — нет. Если задача — «ответь по огромному тексту целиком», это выбор в пользу Llama; если задача — много коротких запросов на потоке и важна скорость, Mistral дешевле по железу.

А если нужен русский покрепче

Qwen2.5 из таблицы уже хорошо работает с русским — это безопасный дефолт, не требующий экзотики. Существенно «заточенных под русский» моделей в официальной библиотеке Ollama на момент проверки — мало; отдельные русскоязычные семейства (например, GigaChat от Sber выпущен с открытыми весами и описанием архитектуры в arXiv:2506.09440) чаще доступны через собственный API или выкладываются в Hugging Face в формате GGUF, а не готовым тегом в Ollama. GGUF-файл можно подключить в Ollama через Modelfile — но точное наличие и название тега нужно сверять на библиотеке в момент установки, а не по памяти: модельный ряд меняется каждые месяцы, и выдуманный тег — самая частая причина «команда не работает». Для практического контура на русском безопаснее брать qwen2.5:7b, а редкие «строго-русские» модели заводить отдельным Modelfile, когда вы точно знаете, откуда качаете веса.

Часть IV. Квантование = память, и это не то же, что размер в параметрах

«7B-модель» говорит о числе параметров, но сколько она реально займёт RAM — определяет квантование: вес хранят с меньшей точностью, жертвуя каплей качества ради памяти и скорости. В Ollama квант — часть тега. Один и тот же Qwen2.5 на 7B:

qwen2.5:7b-instruct-q4_K_M   →  4.7 GB   (рабочий дефолт: качество/память в балансе)
qwen2.5:7b-instruct-q8_0     →  8.1 GB   (выше качество, почти fp16, дороже по памяти)
qwen2.5:7b-instruct-fp16     →  15 GB    (максимум качества, минимум потерь, тяжёлая)

(размеры те же, что и на странице библиотеки: q4_K_M 4.7 / q8_0 8.1 / fp16 15 GB). Практическое правило:

  • q4_K_M — ставить по умолчанию. Это то квантование, под которое обычно хватает 8–16 GB VPS, и потеря качества против fp16 на бытовых задачах почти не заметна.
  • q8_0 / fp16 — когда задача действительно просит максимум и есть запас памяти. Разница 4.7→15 GB в разы, а прирост — нет.

Но вес модели — ещё не вся память. Во время генерации живёт KV-кэш: чем больше контекст и чем длиннее диалог, тем больше RAM поверх файла весов. Поэтому ориентир по свободной памяти для комфортной 7–8B модели на q4_K_M — ~6–8 GB (вес + KV-кэш + служебное; это оценочный коридор, а не точная константа). Тот же MemoryMax из Части II стоит ставить выше этой цифры, иначе Ollama начнёт вытеснять PHP-FPM. Экономика памяти сервера под конфигурацию пула разобрана рядом — PHP-FPM: движок под WordPress: модель и пул делят один и тот же RAM, считать надо их вместе, не по отдельности.

Часть V. Интеграция с PHP: локальная модель — это другой base URL

Главное для программиста: Ollama отдаёт OpenAI-совместимый API. Значит, код, который уже умеет обращаться к облаку, переключается на локаль одной строкой — адресом. Точка входа — POST http://127.0.0.1:11434/v1/chat/completions.

PHP
<?php
// Локальный вызов модели вместо облачного: тот же OpenAI-формат тела.
function call_model_local( string $system, string $user, string $model = 'qwen2.5:7b' ): string {
    $resp = wp_remote_post( 'http://127.0.0.1:11434/v1/chat/completions', array(
        'timeout' => 120,                       // локальная генерация медленнее облака на CPU — таймаут крупнее
        'headers' => array( 'Content-Type' => 'application/json' ),
        'body'    => wp_json_encode( array(
            'model'    => $model,
            'messages' => array(
                array( 'role' => 'system', 'content' => $system ),
                array( 'role' => 'user',   'content' => $user   ),
            ),
            'temperature'    => 0.2,
            'max_tokens'     => 800,
            'stream'         => false,
        ) ),
    ) );
    if ( is_wp_error( $resp ) ) return '';
    $data = json_decode( wp_remote_retrieve_body( $resp ), true );
    return (string) ( $data[ 'choices' ][0][ 'message' ][ 'content' ] ?? '' );
}

Что меняется относительно облачного вызова и почему это удобно:

  • Ключ не нужен вовсе. На localhost модель слушает без аутентификации — локальный хост «доверенный». Это не дыра, пока порт не торчит наружу; но и секрет в .env прятать не от чего. Секретная гигиена с .env остаётся в силе только для облачных ключей — см. Три конфига WordPress.
  • Тот же контракт ответа. choices[0].message.content — OpenAI-формат, поэтому RAG-контур из предыдущей статьи переключается на локаль без переделки логики — меняется только call_model на call_model_local. Подложка, порог score, guard — всё остаётся.
  • stream = true для интерактива отдаёт SSE-поток (текст «прирастает» по мере генерации); механика работы с потоками в браузере — Потоки в браузере: SSE и WebSocket. На CPU генерация идёт заметно медленнее облака, поэтому для чата стриминг почти обязателен — иначе пользователь смотрит в пустой экран 20 секунд.

Если вместо прямой интеграции нужен «готовый помощник», на сайте есть живая страница чат-ассистента — поговорить с моделью можно там, а локальный endpoint подключается тем же OpenAI-совместимым обращением: https://codestack.space/ai-pomoshhnik-dlja-koda/.

Часть VI. Облако против локали: как считать компромисс

Нельзя просто «поставить Ollama и забыть» — вы меняете один набор ограничений на другой. Честная таблица, по чему они расходятся:

ОсьОблакоЛокаль (Ollama)
Приватность данныхтекст уходит на сервер вендораданные не покидают сервер ✅
Пиковое качествофлагманские моделилокальная 7–8B слабее на сложном
Ценаоплата за токеныоплата за железо + электричество
Альтернативный сбойзависит от вендора и сетиживёт на вашем сервере
Латентностьчерез сетьчерез localhost ✅
Масштабмасштабирует вендорупирается в ваше CPU/GPU

Практический вывод: гибрид, а не «или/или». Чувствительное (логи с токенами, клиентские документы, внутренний чат-бот) — в локаль; сложное не-секретное (маркетинговый слоган, генерация по длинному общедоступному корпусу, где нужна вся мощность флагмана) — в облако. Тот же принцип локального против облачного наглядно демонстрирует интерактивная песочница, где один текст обрабатывают обе стороны и можно сравнить результат: https://codestack.space/sravnenie-ii/. Для приватных данных локаль — не «а может быть», а «надо»; для пикового качества — облако пока впереди, и наивно требовать от 7B-модели на CPU результата флагмана.

Инструменты под задачу

Самое полезное после установки — не код, а калибровка ожиданий: задайте локально запущенной модели реальный вопрос своей предметной области и посмотрите на ответ. Универсальный чат-ассистент ниже работает по тем же OpenAI-совместимым моделям, так что им удобно сравнивать «как отвечает облако» против ощущения от локального качества, чтобы заранее понять, какие задачи вообще стоит переносить на своё железо:

ИИ-помощник по коду
Гость
Токены: — / 50 000
Гостевой лимит — 50 000 токенов. Зарегистрируйтесь для лимита 200 000 токенов.

👋 Привет! Я AI-помощник на базе Qwen 3 Coder.

Задавайте вопросы, вставляйте код через кнопку «Код» — я проверю, объясню или перепишу.

Enter — отправить · Shift+Enter — новая строка

Чек-лист граблей

  • Пробросили 11434 в интернет без аутентификации → открытый генератор текста для всего света, жрёт ваш CPU; слушайте localhost, наружу — только через прокси с доступом.
  • Ставите «самую умную» fp16 без запаса памяти → модель вытесняет PHP-FPM и кладёт сервер; квант выбирайте по железу, не по амбиции.
  • Забываете KV-кэш при расчёте RAM → вес 4.7 GB не значит «хватит 5 GB»: нужен запас на контекст и диалог.
  • Считаете квант «технической мелочью» → q4_K_M против fp16 — это разница качества/памяти в разы; для бытовых задач q4_K_M — рабочий дефолт.
  • Ищете «строго-русскую» модель по памяти и ставите несуществующий тег → команда падает; наличие тега сверяется на ollama.com в момент установки, надёжный дефолт для русского — Qwen2.5.
  • Ожидаете облачного качества от локальной 7B → разочарование; на сложном облако пока впереди, для приватного же выбора — локаль.
  • Требуете мгновенного ответа от локальной модели на CPU → при stream=false пользователь смотрит в пустой экран; для интерактива нужен стриминг.
  • Ставили крупный timeout как для облака → локальная генерация дольше, короткий таймаут обрывает ответ на середине; таймаут локального вызова — с запасом.
  • Дублируете секрет в .env для локального вызова → лишний ключ там, где аутентификации на localhost нет; .env нужен для облачных ключей.
  • Не ограничили модель на общем сервере → одна генерация съедает RAM/ЦП сайта; MemoryMax и лимиты юнита обязательны.

👤 Зарегистрируйтесь, чтобы добавить материал в закладки.