SSD Nodes Learn 🎉 VPS от $5.50/мес
Руководства Matt ConnorАвтор: Matt Connor · Обновлено 2026-08-21

Запуск llama.cpp сервера на VPS через systemd

Узнайте, как собрать llama-server из конкретного тега, настроить API для работы с GGUF моделями и ограничить потребление памяти через unit-файл systemd для стабильной работы.

Что вы создаете

Запуск сервера llama.cpp на VPS подразумевает использование одного бинарного файла, llama-server, который загружает единственный файл модели GGUF и отвечает на HTTP-запросы через API, совместимый с OpenAI. Укажите любой клиент OpenAI на http://127.0.0.1:8080/v1, и он заработает. Установка — это лишь половина дела.

Остальная работа относится к эксплуатации: фиксация версии, привязка порта к localhost, написание unit-файла systemd и определение поведения системы при нехватке оперативной памяти. Именно это рассматривается в данном руководстве. Если вы еще не выбрали между двумя очевидными вариантами, сначала прочитайте сравнение преимуществ и недостатков Ollama и llama.cpp, так как это руководство описывает техническую реализацию, которую сравнение намеренно опускает.

Выберите тег релиза и запишите его

Проект llama.cpp помечает тегом практически каждый merge, поэтому теги представляют собой номера сборок. b10488 — самый свежий на 18 августа 2026 года. В проекте нет долгоживущих стабильных веток, поэтому «latest» — это постоянно меняющаяся цель, и версия, которую вы протестировали, является единственной, которую вы можете поддерживать. Выберите тег, запишите его и используйте эту же строку при клонировании, в имени бинарного файла и в своих заметках.

Для каждого тега также выпускаются предварительно собранные архивы. Для x86 VPS только с CPU это llama-b10488-bin-ubuntu-x64.tar.gz, а архив для arm64 находится рядом, если вы используете ARM VPS вместо x86.

curl -LO https://github.com/ggml-org/llama.cpp/releases/download/b10488/llama-b10488-bin-ubuntu-x64.tar.gz
tar tf llama-b10488-bin-ubuntu-x64.tar.gz | head

Выведите список содержимого архива перед распаковкой, чтобы понимать, куда будут помещены файлы. Эти бинарные файлы скомпилированы с привязкой к библиотеке C того образа, в котором они были собраны, поэтому в старых дистрибутивах они не запускаются с ошибкой, указывающей на версию GLIBC_, которая не установлена. Сборка из исходного кода на небольшом VPS занимает несколько минут и полностью устраняет этот класс проблем, поэтому ниже описан именно этот путь.

Сборка llama-server из зафиксированного тега

sudo apt update
sudo apt install -y build-essential cmake git libssl-dev
git clone --depth 1 --branch b10488 https://github.com/ggml-org/llama.cpp
cd llama.cpp
cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DLLAMA_BUILD_TESTS=OFF -DLLAMA_BUILD_EXAMPLES=OFF
cmake --build build --config Release -t llama-server -j 2

--branch b10488 в --depth 1 клоне переключает состояние на этот тег и ничего более, поэтому сборка не изменится, пока вы работаете.

libssl-dev важен, так как опция LLAMA_OPENSSL включена по умолчанию, и именно она позволяет бинарному файлу загружать модели по HTTPS в дальнейшем. Без заголовков этап настройки завершается ошибкой.

-DBUILD_SHARED_LIBS=OFF создает один автономный бинарный файл. Сборка по умолчанию размещает разделяемые библиотеки рядом с исполняемым файлом, поэтому копирование только исполняемого файла в /usr/local/bin приводит к ошибке error while loading shared libraries: libllama.so.

-t llama-server собирает только цель server. Сборка по умолчанию также компилирует другие инструменты и тесты, что на VPS с двумя ядрами занимает несколько лишних минут на файлы, которые вы никогда не будете запускать.

-j 2 используется намеренно. Каждый параллельный процесс компиляции занимает свой объем оперативной памяти, поэтому -j $(nproc) на тарифе с малым объемом ресурсов заканчивается c++: fatal error: Killed signal terminated program cc1plus — это механизм OOM killer ядра, который останавливает компилятор. Уменьшите количество потоков или добавьте swap на время сборки.

Один флаг, который вы, возможно, захотите изменить: GGML_NATIVE включен по умолчанию, поэтому компилятор ориентируется на процессор, на котором выполняется сборка. Это именно то, что нужно, если вы собираете программу на той же машине, где она будет работать. Если вы собираете бинарный файл один раз, чтобы скопировать его на другой хост, добавьте -DGGML_NATIVE=OFF, иначе программа, использующая инструкции, которых нет у другого процессора, завершится с ошибкой Illegal instruction (core dumped) при первой попытке вывода (inference).

Установите его под именем, содержащим номер тега.

./build/bin/llama-server --version
sudo install -m 755 build/bin/llama-server /usr/local/bin/llama-server-b10488
sudo ln -sfn /usr/local/bin/llama-server-b10488 /usr/local/bin/llama-server

--version выводит номер сборки и коммит. Он должен совпадать с тегом, который вы выбрали. Если это не так, значит, вы собрали что-то другое. Использование номера в имени файла и создание символической ссылки на него означает, что обновление выполняется одной командой ln -sfn и перезапуском, а откат — той же командой с указанием старого номера.

Получение модели GGUF и проверка диска

GGUF — это формат единого файла, который загружает llama.cpp. Один файл содержит веса, токенизатор и метаданные, поэтому устанавливать что-либо еще не требуется. Суффикс в имени файла обозначает квантование — точность, с которой хранятся веса: Q4_K_M представляет собой 4-битное смешение, Q8_0 — 8-битное, а f16 — неквантованный файл половинной точности.

Перед загрузкой создайте сервисную учетную запись и каталог для моделей.

sudo useradd --system --home /srv/llama --create-home --shell /usr/sbin/nologin llama
sudo install -d -o llama -g llama /srv/models
df -h /srv

Сервер может самостоятельно загрузить модель с помощью -hf; это самый быстрый способ убедиться, что сборка работает.

sudo -u llama env LLAMA_CACHE=/srv/models /usr/local/bin/llama-server \
  -hf ggml-org/gemma-3-1b-it-GGUF:Q4_K_M --host 127.0.0.1 --port 8080

LLAMA_CACHE задает каталог для загрузки. Без этого параметра файл попадет в ~/.cache/llama.cpp под учетной записью, которая выполнила команду, что является неверным местом для сервиса, чей домашний каталог вы собираетесь сделать недоступным для чтения. После этого запустите ls -lh /srv/models, так как кэшированное имя файла формируется на основе имени репозитория, а не исходного имени файла.

Для сервиса выполняйте загрузку в выбранный вами путь, чтобы юнит-файл имел стабильную точку доступа.

sudo -u llama curl -L --output-dir /srv/models -O \
  https://huggingface.co/ggml-org/gemma-3-1b-it-GGUF/resolve/main/gemma-3-1b-it-Q4_K_M.gguf

Дисковое пространство — это ограничение, с которым сталкиваются в первую очередь. Ниже приведены опубликованные размеры файлов для двух моделей, проверенные 18 августа 2026 года.

ChartGGUF file size on disk, published figures, 18 August 2026
The data behind this chart
[
  {
    "label": "gemma-3-1b-it Q4_K_M",
    "size_gb": 0.81
  },
  {
    "label": "gemma-3-1b-it Q8_0",
    "size_gb": 1.07
  },
  {
    "label": "gemma-3-1b-it f16",
    "size_gb": 2.01
  },
  {
    "label": "gpt-oss-20b MXFP4",
    "size_gb": 12.11
  }
]

4-битный файл для модели 1B занимает 0.81 ГБ. Та же модель без квантования занимает 2.01 ГБ, таким образом, выбор формата меняет значение более чем в два раза. Модель 20B в формате MXFP4 занимает 12.11 ГБ, что не помещается на диск многих базовых тарифных планов, к тому же после загрузки её еще нужно считать в память.

Проверяйте df -h перед каждой загрузкой. Корневая файловая система, заполненная во время передачи 12 ГБ данных, блокирует работу всех остальных процессов, которым требуется запись, включая журнал.

Запустите его вручную один раз и проверьте

sudo -u llama /usr/local/bin/llama-server \
  --model /srv/models/gemma-3-1b-it-Q4_K_M.gguf \
  --host 127.0.0.1 --port 8080 \
  --ctx-size 4096 --parallel 1 --threads 2 --no-webui

Во втором сеансе запросите у сервера, готов ли он к работе.

curl -s http://127.0.0.1:8080/health

Пока файл загружается, вы будете получать HTTP 503 и следующее тело ответа:

{"error":{"code":503,"message":"Loading model","type":"unavailable_error"}}

Когда сервер будет готов, тело ответа примет вид {"status": "ok" }. После этого отправьте реальный запрос.

curl -s http://127.0.0.1:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model":"local","messages":[{"role":"user","content":"Say hello in five words."}]}'

JSON-объект с массивом choices означает, что сервер работает корректно. Поле model присутствует, так как клиенты OpenAI всегда его отправляют. На этом сервере загружена только одна модель, поэтому данное значение не используется для выбора.

OpenAI-совместимый API и другие функции на этом порту

POST /v1/chat/completions, POST /v1/completions и POST /v1/embeddings — это маршруты, совместимые с OpenAI, а GET /v1/models возвращает информацию о загруженной модели. GET /health — это упомянутая выше проверка готовности, GET /props возвращает текущие настройки сервера, а GET /metrics предоставляет счетчики Prometheus, если запуск выполнен с флагом --metrics.

Любой SDK для OpenAI будет работать, если вы укажете базовый URL http://127.0.0.1:8080/v1 и передадите любую непустую строку в качестве API-ключа. Проверка ключа не выполняется, пока вы не зададите --api-key самостоятельно.

Не принимайте заявленные показатели пропускной способности за ориентир для своего плана. Скорость инференса на CPU зависит от количества ядер, пропускной способности памяти и соседей, с которыми вы делите хост, поэтому измерьте количество токенов в секунду на своем оборудовании и считайте этот результат истинным. Время ожидания из-за «шумного соседа» проявляется здесь как изменение скорости генерации в течение дня.

Оставьте сервис на 127.0.0.1 и установите перед ним прокси

--host по умолчанию использует 127.0.0.1, поэтому сервер недоступен извне, пока вы не измените этот параметр. Оставьте его как есть. В llama-server отсутствуют модель пользователей, ограничение частоты запросов (rate limit) и полезные журналы аудита, а единственный встроенный механизм контроля — это --api-key, который сравнивает одну строку. Открытый порт для инференса — это бесплатные вычислительные мощности для любого, кто его обнаружит. Ошибка с Ollama имеет те же последствия: ограничение доступа к API локальной модели применимо здесь в полной мере.

Выполняйте TLS (transport layer security) termination в nginx и проксируйте запросы на loopback-порт.

server {
    listen 443 ssl;
    server_name llm.example.com;

    location /v1/ {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_buffering off;
        proxy_read_timeout 600s;
    }
}

proxy_buffering off необходим для потоковой передачи. Если буферизация включена, nginx удерживает server-sent events (SSE) до завершения ответа, из-за чего клиент ожидает в тишине, а затем получает весь ответ целиком. proxy_read_timeout 600s позволяет обрабатывать длительные генерации, так как значение по умолчанию в 60 секунд превращает медленный ответ в 504 Gateway Time-out. Получите сертификат с помощью Certbot и Let's Encrypt в nginx.

Юнит systemd

Запишите /etc/systemd/system/llama-server.service.

[Unit]
Description=llama.cpp server
After=network-online.target
Wants=network-online.target

[Service]
User=llama
Group=llama
Environment=LLAMA_ARG_MODEL=/srv/models/gemma-3-1b-it-Q4_K_M.gguf
Environment=LLAMA_ARG_HOST=127.0.0.1
Environment=LLAMA_ARG_PORT=8080
Environment=LLAMA_ARG_CTX_SIZE=4096
Environment=LLAMA_ARG_N_PARALLEL=1
Environment=LLAMA_ARG_THREADS=2
ExecStart=/usr/local/bin/llama-server --no-webui
Restart=on-failure
RestartSec=5
TimeoutStopSec=30
MemoryHigh=3G
MemoryMax=3500M
OOMPolicy=stop
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes

[Install]
WantedBy=multi-user.target

Настройки находятся в строках Environment=, так как llama-server считывает переменные LLAMA_ARG_* для большинства флагов, а аргумент командной строки переопределяет соответствующую переменную. Это позволяет изменять размер контекста в одном месте и сохраняет ExecStart достаточно коротким для быстрого чтения.

ProtectSystem=strict делает всю файловую систему доступной только для чтения для этого юнита, что допустимо, так как сервер только считывает модель. Добавьте ReadWritePaths=/srv/models, если вы хотите, чтобы сам сервис загружал модели с помощью -hf. ProtectHome=yes скрывает /home и /root, и это вторая причина хранить модели в /srv: при включенном ProtectHome путь по умолчанию ~/.cache/llama.cpp вообще не виден процессу.

sudo systemctl daemon-reload
sudo systemctl enable --now llama-server
systemctl status llama-server
curl -s http://127.0.0.1:8080/health
journalctl -u llama-server -n 50 --no-pager

enable --now — это половина дела, которую люди пропускают. Без enable сервер исчезнет после следующей перезагрузки. Если вам нужны запланированные задачи вокруг сервиса, например, ночная проверка наличия нового релиза, systemd-сервис с таймером является подходящим механизмом для этого.

Определите поведение при нехватке памяти (OOM) заранее

Использование памяти состоит из двух частей, и они по-разному реагируют на ограничения. Файл модели по умолчанию отображается в память (memory-mapped), поэтому его страницы привязаны к файлу: ядро может выгрузить их и считать снова с диска. KV-кэш, представляющий собой состояние для каждого токена, которое сервер хранит для всех активных диалогов, является анонимной памятью. Она не может быть выгружена, поэтому именно из-за неё процесс завершается принудительно.

Именно поэтому два ограничения в юните выполняют разные задачи. MemoryHigh=3G — это мягкое ограничение (soft limit): при его превышении ядро оказывает давление на cgroup, поэтому отображенные страницы модели вытесняются и считываются обратно с диска при обработке следующего токена. Сервис продолжает работать, но замедляется. MemoryMax=3500M — это жесткое ограничение (hard limit): при его превышении процесс завершается, и журнал событий прямо указывает на это.

llama-server.service: A process of this unit has been killed by the OOM killer.

Установите --ctx-size самостоятельно. Значение по умолчанию — 0, что соответствует контексту, на котором обучалась модель, и для современных моделей с длинным контекстом это приводит к выделению очень большого KV-кэша при запуске. В результате сервис завершается, не успев обработать ни одного запроса. --parallel умножает эти затраты, так как каждый слот хранит собственное состояние диалога, поэтому оставьте значение 1, пока не убедитесь, что вам действительно нужна параллельная обработка.

Благодаря Restart=on-failure завершенный сервис перезапускается. Если он завершается при каждом запуске, systemd прекращает попытки, и systemctl status выводит start request repeated too quickly. Это правильное поведение: цикл перезапуска, который считывает 12 GB файл каждые пять секунд, хуже, чем простой сервиса. Исправьте ограничение или размер контекста, затем очистите состояние с помощью sudo systemctl reset-failed llama-server.

Отслеживайте реальные показатели с помощью systemctl show llama-server -p MemoryCurrent во время выполнения запроса. В Ограничение памяти и процессора для процесса с помощью systemd эти директивы описаны более подробно.

Избегайте использования swap для данной нагрузки. Вытеснение модели в swap превращает генерацию каждого токена в случайное чтение с диска. Отображение файла модели в память (memory mapping) достигает того же эффекта с меньшим вредом, так как ядро считывает нужные страницы напрямую из файла.

Когда Ollama — более подходящее решение

Здесь пути расходятся. Выбирайте llama-server, если вам нужен один процесс с заданными флагами, зафиксированной сборкой и выбранным файлом, где конфигурация остается неизменной, так как другие компоненты не запущены.

Выбирайте Ollama, если вам требуется управление моделями: загрузка моделей по имени, хранение нескольких моделей на диске, выгрузка неактивных моделей и обновление одной командой вместо пересборки. Это реальная работа, которую в противном случае пришлось бы автоматизировать самостоятельно. Запуск Ollama на VPS — это та же задача, но с иным балансом преимуществ. Оба решения предоставляют API, совместимый с OpenAI, поэтому клиентский код будет работать при переключении в любую сторону.

Обновление зафиксированной сборки

Замените bNNNNN на тег, на который вы переходите.

cd llama.cpp
git fetch --tags
git checkout bNNNNN
cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DLLAMA_BUILD_TESTS=OFF -DLLAMA_BUILD_EXAMPLES=OFF
cmake --build build --config Release -t llama-server -j 2
sudo install -m 755 build/bin/llama-server /usr/local/bin/llama-server-bNNNNN
sudo ln -sfn /usr/local/bin/llama-server-bNNNNN /usr/local/bin/llama-server
sudo systemctl restart llama-server

Старый бинарный файл остается на диске, поэтому откат выполняется одной командой ln -sfn до llama-server-b10488 и одним перезапуском. Перед обновлением ознакомьтесь с примечаниями к выпуску (release notes). Файлы GGUF версионируются, и старые версии продолжают загружаться, однако флаги переименовываются: --mlock и --no-mmap уже считаются устаревшими в пользу --load-mode, и unit-файл, передающий удаленный флаг, при запуске выдаст ошибку о нераспознанном аргументе.

Типичные ошибки и сообщения, которые вы увидите

error while loading shared libraries: libllama.so после копирования бинарного файла в другое место. Сборка по умолчанию создает общие библиотеки рядом с ним. Выполните пересборку с -DBUILD_SHARED_LIBS=OFF или скопируйте всю директорию build/bin.

Illegal instruction (core dumped) при запуске или при первом запросе. Бинарный файл был скомпилирован с GGML_NATIVE для процессора, архитектура которого отличается от текущей. Выполните пересборку на этой машине или настройте параметры с помощью -DGGML_NATIVE=OFF.

c++: fatal error: Killed signal terminated program cc1plus во время сборки. Компилятор был завершен принудительно из-за нехватки оперативной памяти. Уменьшите значение -j или добавьте swap на время сборки, а затем удалите его.

curl: (7) Failed to connect ... Connection refused при попытке подключения с вашего ноутбука. Это ожидаемое поведение: сервер слушает только loopback-адрес на VPS. Протестируйте работу на самой VPS или откройте туннель с помощью ssh -L 8080:127.0.0.1:8080 user@your-vps и используйте http://127.0.0.1:8080 локально.

HTTP 503 с "message":"Loading model" в течение первых секунд или минут после перезапуска. Чтение файла размером в несколько гигабайт требует времени, а systemd помечает юнит как активный сразу после запуска процесса, задолго до того, как модель будет загружена в память.

Запросы зависают, а затем возвращают 504 Gateway Time-out. Прокси-сервер прекратил ожидание до того, как модель завершила работу. Увеличьте значение proxy_read_timeout и отключите proxy_buffering, чтобы токены поступали клиенту по мере их генерации.

Юнит постоянно перезапускается и затем останавливается с ошибкой start request repeated too quickly. Какой-то процесс завершает его при каждом запуске. Проверьте journalctl -u llama-server на наличие записи от OOM killer, затем уменьшите --ctx-size, уменьшите --parallel или увеличьте MemoryMax.

FAQ

Что лучше запустить на VPS: сервер llama.cpp или Ollama?

Используйте llama-server, если вам нужно зафиксировать конкретную сборку, передать точные флаги и хранить одну модель в одном файле, который никто не обновит без вашего ведома. Используйте Ollama, если вам нужно управление моделями и обновления одной командой, так как загрузка моделей по имени, хранение нескольких штук на диске и выгрузка неактивных — это работа, которую в противном случае пришлось бы автоматизировать самостоятельно. Оба варианта предоставляют API, совместимый с OpenAI, поэтому клиентский код не изменится, если вы решите переключиться позже.

На какой версии llama.cpp стоит зафиксироваться?

На любом теге, который вы успешно собрали и протестировали. llama.cpp присваивает тег почти каждому слиянию, и эти имена представляют собой номера сборок, например b10488, который был самым новым на 18 августа 2026 года. Отдельной стабильной ветки не существует, поэтому «текущая» версия меняется несколько раз в день. Клонируйте репозиторий с помощью --branch <tag>, установите бинарный файл под именем, содержащим этот тег, и укажите на него через символическую ссылку — так обновление и откат будут выполняться одной командой.

Сколько оперативной памяти нужно для llama-server?

Начните с размера GGUF-файла, затем добавьте объем KV-кэша, который растет вместе с --ctx-size и количеством слотов --parallel. Опубликованные цифры не заменят замеры вашей собственной системы, так как итоговое потребление зависит от модели, квантования и разрешенного контекста. Запустите systemctl show llama-server -p MemoryCurrent во время выполнения запроса и ориентируйтесь на полученное значение.

Почему /health возвращает 503 с сообщением "Loading model"?

Процесс запущен, но файл модели еще не загружен в память, поэтому сервер отвечает {"error":{"code":503,"message":"Loading model","type":"unavailable_error"}}. Это нормальное поведение после каждого перезапуска, которое длится столько времени, сколько занимает чтение файла. Проблема возникает только в том случае, если клиент или прокси воспринимает этот первый 503 как критическую ошибку. Опрашивайте /health, пока он не вернет {"status": "ok" }.

Можно ли открывать llama-server напрямую в интернет?

Не привязывайте его к 0.0.0.0 и не открывайте порт. В нем нет учетных записей, ограничения частоты запросов и логов запросов, пригодных для аудита, а единственная встроенная проверка — это --api-key, которая сравнивает одну строку. Оставьте привязку по умолчанию 127.0.0.1, поставьте перед ним nginx с TLS и настройте --api-key, чтобы одна ошибка в конфигурации прокси не сделала модель доступной для всех.